Scala - Use uppercase first letter when decoding Json values with Zio JSON - scala

I'm using Zio Json library to attempt to decode the following:
object BasicInfo {
private case class BasicInfoWire(
defaultPaymentMethod: DefaultPaymentMethod,
IdentityId__c: String,
sfContactId__c: String,
balance: BigDecimal,
currency: String
)
given JsonDecoder[BasicInfo] = DeriveJsonDecoder.gen[BasicInfoWire].map {
case BasicInfoWire(defaultPaymentMethod, IdentityId__c, sfContactId__c, balance, currency) =>
IdentityId__c match {
case "" => BasicInfo(defaultPaymentMethod, None, sfContactId__c, (balance.toDouble * 100).toInt, currency)
case id => BasicInfo(defaultPaymentMethod, Some(id), sfContactId__c, (balance.toDouble * 100).toInt, currency)
}
}
}
As Scala doesn't allow variable names to start with uppercase, I'm getting a Not Found error when referring to the variable inside the case statement.
How can I amend this to make it work?

object BasicInfo {
private case class BasicInfoWire(
defaultPaymentMethod: DefaultPaymentMethod,
IdentityId__c: String,
sfContactId__c: String,
balance: BigDecimal,
currency: String
)
given JsonDecoder[BasicInfo] = DeriveJsonDecoder.gen[BasicInfoWire].map {
case BasicInfoWire(defaultPaymentMethod, identityId, sfContactId__c, balance, currency) =>
identityId match {
case "" => BasicInfo(defaultPaymentMethod, None, sfContactId__c, (balance.toDouble * 100).toInt, currency)
case id => BasicInfo(defaultPaymentMethod, Some(id), sfContactId__c, (balance.toDouble * 100).toInt, currency)
}
}
}
Solved it with this.

Related

Scala generic case class with optional field

I have the following generic case class to model a resources in an HTTP API (we use Akka HTTP):
case class Job[Result](
id: String,
result: Option[Result] = None,
userId: String,
)
and want to specify multiple, named, variations of a Job, were some of them provide a result, while others don't:
case class FooResult(data: String)
type FooJob = Job[FooResult]
// BarJob does not have any result, thus result should be None
case class BarJob = Job[/*what do to?*/]
my question is, is there any way to define Job as a generic case class where the type parameter only needs to be specified when the field result is Some ? What I would like to do is something like:
// result is by default None in Job, so I don't want to specify any type here
case class BarJob = Job
Or perhaps there's better ways to do this, rather than using type aliases?
One option is to use base traits for Jobs with and without results:
trait Job {
def id: String
def userId: String
}
trait JobWithResult[T] extends Job {
def result: T
}
case class FooResult(data: String)
case class FooJob(id: String, userId: String, result: FooResult) extends JobWithResult[FooResult]
case class BarJob(id: String, userId: String) extends Job
You can then use match on a Job to see whether it has a result or not.
job match {
case FooJob(id, userId, foo) => println(s"FooJob with result $foo")
case j: JobWithResult[_] => println(s"Other job with id ${j.id} with result")
case j: Job => println(s"Job with id {$j.id} without result")
}
This assumes that the result is not actually optional.
As pointed out in the comments, the Option is "unnecessary".
I submit, it's not so much "unnecessary" as indicative ... of a way for you to implement what you want without repetitive declarations.
case class Job[Result](id: String, userId: String, result: Option[Result] = None)
object Job {
def void(id: String, userId: String) = Job[Nothing](id, userId)
def apply[R](id: String, userId: String, result: R) = Job(id, userId, Option(result))
}
type FooJob = Job[FooResult]
type VoidJob = Job[Nothing]

How to use scala case class as function parameter to specify different fields to use?

I have some duplication of code due to having to do some grouping on 3 different fields in a case class and then populate a new case class with those. Since they share a common schema it should be possible for me to do a function that can take the input of the 3 different fields and populate accordingly. However, I am not exactly sure how to do this.
Schemas:
case class Transaction(
senderBank: Bank,
receiverBank: Bank,
intermediaryBank: Bank)
case class Bank(
name: String,
country: Option[String],
countryCode: Option[String])
case class GroupedBank(
name: String,
country: Option[String],
countryCode: Option[String],
bankType: String)
Current function I tried to do:
def groupedBank(transactionSeq: Seq[Transaction], bankName: Bank, bankTypeString: String): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(_ => bankName.name).map {
case (key, transactionSeq) =>
val bankGroupedSeq = transactionSeq.map(_ => {
GroupedBank(
name = bankName.name,
country = bankName.country,
countryCode = bankName.countryCode,
bankType = bankTypeString)
})
bankGroupedSeq
}
}
I need to do the grouping for SenderBank, receiverBank, and intermediaryBank. However, I am not sure how to refer to them correctly in the function parameter bankName. So for SenderBank I would want to do something like Transaction.senderBank, which would point to the correct fields for name, country and so on for senderBank. For receiverBank it should be similar, so Transactions.receiverBank, which then refers to the correct fields for receiverBank and so on. And again for intermediaryBank the same logic. My question is therefore how can I accomplish something like this or is there another way that would be more appropriate?
You can pass a function to extract the bank with the correct type from a transaction:
def groupedBank(
transactionSeq: Seq[Transaction],
getBank: Transaction => Bank,
bankTypeString: String
): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(getBank(_).name).map {
case (key, transactionSeq) =>
transactionSeq.map { transaction =>
val bank = getBank(transaction)
GroupedBank(
name = bank.name,
country = bank.country,
countryCode = bank.countryCode,
bankType = bankTypeString)
}
}
}
And then call it like this:
groupedBank(transactionSeq, _.senderBank, "sender")
It could also be a good idea to abstract the bank type concept into a separate trait:
sealed trait BankGroup {
def name: String
def getBank(transaction: Transaction): Bank
def groupBanks(transactionSeq: Seq[Transaction]): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(getBank(_).name).map {
case (key, transactionSeq) =>
transactionSeq.map { transaction =>
val bank = getBank(transaction)
GroupedBank(
name = bank.name,
country = bank.country,
countryCode = bank.countryCode,
bankType = name)
}
}
}
}
object BankGroup {
object Sender extends BankGroup {
def name: String = "sender"
def getBank(transaction: Transaction): Bank = transaction.senderBank
}
object Receiver extends BankGroup {
def name: String = "receiver"
def getBank(transaction: Transaction): Bank = transaction.receiverBank
}
object Intermediary extends BankGroup {
def name: String = "intermediary"
def getBank(transaction: Transaction): Bank = transaction.intermediaryBank
}
val values: Seq[BankGroup] = Seq(Sender, Receiver, Intermediary)
def byName(name: String): BankGroup = values.find(_.name == name)
.getOrElse(sys.error(s"unknown bank type: $name"))
}
And you can call it in one of those ways:
BankGroup.Sender.groupBanks(transactionSeq)
BankGroup.byName("sender").groupBanks(transactionSeq)

Play Slick: How to fetch selected fields from a DB table in play-slick 3.0.0?

I am new to play framework. I am working on a play slick based application where I want to fetch a list of objects from DB which will contains some selected fields. For fetching all the fields I am using following code:
case class Mail(txID: String,
timeStamp: Long,
toUserID: String,
mailContent: String,
mailTemplateFileName: String,
fromID: String,
toID: String
)
def getLogFromIDFuture(userID: String): Future[Option[List[Mail]]] = cache.getOrElseUpdate[Option[List[Mail]]](userID) {
val resultingUsers = db.run(mailsData.filter(x => x.toUserID === userID).result)
val res = Await.result(resultingUsers, Duration.Inf)
res.map(t => t) match {
case t if t.nonEmpty =>
Future(Some(t.toList))
case _ => Future(None)
}
}
So my question is how to fetch only timeStamp, toUserID, mailContent, fromID, toID fields as the list of objects like UserMessage(timeStamp: Long, toUserID: String, mailContent: String, fromID: String, toID: String). I tried searching about this but didn't get any convincing answers.
Like I said in the comment you can do this:
def getLogFromIDFuture(userID: String): Future[Option[List[UserMessage]]] = cache.getOrElseUpdate[Option[List[Mail]]](userID) {
val resultingUsers = db.run(mailsData.filter(x => x.toUserID === userID).map(entry =>(entry.timeStamp, entry.toUserID, entry.mailContent, entry.fromID, entry.toID))
.result)// here you have the tuple of things
// add the mapping of the tuple to the UserMessage
val res = Await.result(resultingUsers, Duration.Inf)
res.map(t => t) match {
case t if t.nonEmpty =>
Future(Some(t.toList))
case _ => Future(None)
}
}
You can get rid of that Await.result
resultingUsers.map( match {
case t if t.nonEmpty =>
Some(t.toList)
case _ => None
}
)
Hope it helps.

How to manage scala enum with Anorm?

I want to create user roles by using enums (thought that it would be the best idea).
How should I proceed with this?
My current code looks like this:
object UserRole extends Enumeration {
type UserRole = Value
val admin, user, manager = Value
}
case class User(id: Long, firstname: String, lastname: String, password: String, email: String, role: UserRole)
So how should I define the user "simple":
val simple = {
get[Long]("user.id") ~
get[String]("user.firstname") ~
get[String]("user.lastname") ~
get[String]("user.password") ~
get[String]("user.email") ~
get[UserRole]("user.role") map {
case id~firstname~lastname~password~email~role => User(id, firstname, lastname, password, email, role)
}
}
And how should I save it to database?
CREATE TABLE user (
id integer NOT NULL DEFAULT nextval('user_id_seq'),
firstname varchar(60),
lastname varchar(60),
password varchar(255),
email varchar(60),
role varchar(40)
);
Anorm doesn't support scala enums (and scala enums are pretty bad, in my opinion). What I usually do is create another case class with it's own parser and table space, and JOIN it to relevant queries. The UserRole can be parsed in User by composing it within User.simple.
case class UserRole(id: Long, name: String)
object UserRole {
val simple: RowParser[UserRole] = {
get[Long]("roles.id") ~
get[String]("roles.name") map {
case id~name => UserRole(id, name)
}
}
}
case class User(id: Long, firstname: String, lastname: String, password: String, email: String, role: UserRole)
object User {
val simple: RowParser[User] = {
get[Long]("user.id") ~
get[String]("user.firstname") ~
get[String]("user.lastname") ~
get[String]("user.password") ~
get[String]("user.email") ~
UserRole.simple map {
case id~firstname~lastname~password~email~role =>
User(id, firstname, lastname, password, email, role)
}
}
}
As another alternative, you could model your roles as with a sealed trait.
sealed trait UserRole
case object Admin extends UserRole
case object User extends UserRole
case object Manager extends UserRole
Then provide a anorm reader for your type.
implicit val column: Column[UserRole] = {
Column.nonNull1 { (value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case "Admin" => Right(Admin)
case "User" => Right(User)
case "Manager" => Right(Manager)
case unknown => Left(TypeDoesNotMatch(s"unknown UserRole from $unknown"))
}
}
}
Then you can use the type in your row parser like get[UserRole]
Note the matching on the string, the obviously a pretty budget but you get the idea.
For more details see the docs here, https://playframework.com/documentation/2.4.x/ScalaAnorm#Column-parsers

Using extractors to parse text files

I'm trying to improve a CSV parsing routine and feel that extractors could be useful here but can't figure them out. Suppose there's a file with user ids and emails:
1,alice#alice.com
2,bob#bob.com
3,carol#carol.com
If the User class is defined as case class User(id: Int, email: String) everything is pretty easy with something like
lines map { line =>
line split "," match {
case Array(id, email) => User(id.toInt, email)
}
}
What I don't understand is how to deal with the case where User class can have complex properties e.g
case class Email(username: String, host: string)
case class User(id: Int, email: Email)
You probably want to use a regular expression to extract the contents of the email address. Maybe something like this:
val lines = Vector(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com")
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val EmailRe = """(.*)#(.*\.com)""".r // substitute a real email address regex here
lines.map { line =>
line.split(",") match {
case Array(id, EmailRe(uname, host)) => User(id.toInt, Email(uname, host))
}
}
Here you go, an example using a custom Extractor.
// 1,Alice,21212,Baltimore,MD" -> User(1, Alice, Address(21212, Baltimore, MD))
Define a custom Extractor that creates the objects out of given String:
object UserExtractor {
def unapply(s: String) : Option[User] = try {
Some( User(s) )
}
catch {
// bettor handling of bad cases
case e: Throwable => None
}
}
Case classes to hold the data with a custom apply on Comapnion object on User:
case class Address(code: String, cit: String, county: String)
case class User(id: Int, name: String, address: Address)
object User {
def apply(s: String) : User = s.split(",") match {
case Array(id, name, code, city, county) => User(id.toInt, name, Address(code, city, county) )
}
}
Unapplying on a valid string (in the example valid means the correct number of fields).
"1,Alice,21212,Baltimore,MD" match { case UserExtractor(u) => u }
res0: User = User(1,Alice,Address(21212,Baltimore,MD))
More tests could be added with more custom apply methods.
I'd use a single RegexExtractor :
val lines = List(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com"
)
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val idAndEMail = """^([^,]+),([^#]+)#(.+)$""".r
and define a function that transforms a line to the an User :
def lineToUserWithMail(line: String) : Option[User] =
idAndEMail.findFirstMatchIn(line) map {
case userWithEmail(id,user,host) => User(id.toInt, Email(user,host) )
}
Applying the function to all lines
lines flatMap lineToUserWithMail
//res29: List[User] = List(User(1,Email(alice,alice.com)), User(2,Email(bob,bob.com)), User(3,Email(carol,carol.com)))
Alternatively you could implement custom Extractors on the case classe by adding an unnapply Method. But for that case it wouldn't be worth the pain.
Here is an example for unapply
class Email(user:String, host:String)
object Email {
def unapply(s: String) : Option[(String,String)] = s.split("#") match {
case Array(user, host) => Some( (user,host) )
case _ => None
}
}
"bob#bob.com" match {
case Email(u,h) => println( s"$u , $h" )
}
// prints bob , bob.com
A word of warning on using Regex to parse CSV-data. It's not as easy as you might think, i would recommend to use a CSV-Reader as http://supercsv.sourceforge.net/ which handles some nasty edge cases out of the box.