Scala json parsing for single field class - json

I have following class and Reads Writes for json parsing / writeing
import play.api.libs.json._
import play.api.libs.functional.syntax._
import scala.collection.immutable.{Seq => ISeq, Set => ISet}
case class FileExcInc( isExclude: Boolean, isFile: Boolean, pattern: String) { }
implicit val fEIReads: Reads[FileExcInc] = (
(__ \ "isExclude").read[Boolean] and
(__ \ "isFile").read[Boolean] and
(__ \ "pattern").read[String]
)(FileExcInc.apply _)
implicit val fEIWrites: Writes[FileExcInc] = (
(__ \ "isExclude").write[Boolean] and
(__ \ "isFile").write[Boolean] and
(__ \ "pattern").write[String]
)(unlift(FileExcInc.unapply))
case class DirConf(sourceDir: String, csdataFile: String, fileExcIncSeq: ISeq[FileExcInc]) { }
implicit val dCReads: Reads[DirConf] = (
(__ \ "sourceDir").read[String] and
(__ \ "csdataFile").read[String] and
(__ \ "fileExcIncSeq").read[ISeq[FileExcInc]]
)(DirConf.apply _)
implicit val dCWrites: Writes[DirConf] = (
(__ \ "sourceDir").write[String] and
(__ \ "csdataFile").write[String] and
(__ \ "fileExcIncSeq").write[ISeq[FileExcInc]]
)(unlift(DirConf.unapply))
This compiles OK, but the following does not:
case class Conf(dirConfSeq: ISeq[DirConf]) { }
implicit val cReads: Reads[Conf] = (
(__ \ "dirConfSeq").read[ISeq[DirConf]]
)(Conf.apply _)
implicit val cWrites: Writes[Conf] = (
(__ \ "dirConfSeq").write[ISeq[DirConf]]
)(unlift(Conf.unapply))
It produces following error:
[info] Compiling 17 Scala sources and 1 Java source to /home/jk/workspace/Gen/target/scala-2.12/classes...
[error] /home/jk/workspace/Gen/src/main/scala/com/example/CTool.scala:341: overloaded method value read with alternatives:
[error] (t: scala.collection.immutable.Seq[com.example.CTool.DirConf])play.api.libs.json.Reads[scala.collection.immutable.Seq[com.example.CTool.DirConf]] <and>
[error] (implicit r: play.api.libs.json.Reads[scala.collection.immutable.Seq[com.example.CTool.DirConf]])play.api.libs.json.Reads[scala.collection.immutable.Seq[com.example.CTool.DirConf]]
[error] cannot be applied to (scala.collection.immutable.Seq[com.example.CTool.DirConf] => com.example.CTool.Conf)
[error] (__ \ "dirConfSeq").read[ISeq[DirConf]]
[error] ^
[error] /home/jk/workspace/Gen/src/main/scala/com/example/CTool.scala:345: overloaded method value write with alternatives:
[error] (t: scala.collection.immutable.Seq[com.example.CTool.DirConf])(implicit w: play.api.libs.json.Writes[scala.collection.immutable.Seq[com.example.CTool.DirConf]])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
[error] (implicit w: play.api.libs.json.Writes[scala.collection.immutable.Seq[com.example.CTool.DirConf]])play.api.libs.json.OWrites[scala.collection.immutable.Seq[com.example.CTool.DirConf]]
[error] cannot be applied to (com.example.CTool.Conf => scala.collection.immutable.Seq[com.example.CTool.DirConf])
[error] (__ \ "dirConfSeq").write[ISeq[DirConf]]
What's wrong in the last Reads, Writes? How to create Reads, Writes for class having just one field?

To create a Reads/Writes for a case class with just one field, use map and contramap to convert the field to and from the case class.
implicit val cReads: Reads[Conf] =
(__ \ "dirConfSeq").read[ISeq[DirConf]].map(Conf)
implicit val cWrites: Writes[Conf] =
(__ \ "dirConfSeq").write[ISeq[DirConf]].contramap(_.dirConfSeq)
Even easier is to use the Json.format macro to generate all of the Reads/Writes boilerplate. It handles case classes with any number of fields provided the field names match the JSON. A Format[T] is both a Reads[T] and a Writes[T].
implicit val fEIFormat: Format[FileExcInc] = Json.format[FileExcInc]
implicit val dCFormat: Format[DirConf] = Json.format[DirConf]
implicit val cFormat: Format[Conf] = Json.format[Conf]
See: https://www.playframework.com/documentation/2.6.x/ScalaJsonAutomated

Related

No Json formatter for Option[String]?

I am trying to marshall and un-marshall an Option[String] field to and from JSON. For my use-case, a None value should be marshaled as "null". Here is the code I have:
import org.scalatest.{FlatSpec, Matchers}
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
case class Person(
id: Int,
firstName: Option[String],
lastName: Option[String]
)
object Person {
implicit lazy val personFormat = (
(__ \ "id").format[Int] and
(__ \ "first_name").format[Option[String]] and
(__ \ "last_name").format[Option[String]]
)(Person.apply, unlift(Person.unapply))
}
class PersonSpec extends FlatSpec with Matchers {
"When Person instance is marshaled None fields " should
"be serialized as \"null\" values" in {
val person = Person(1, None, None)
import Person._
val json = Json.toJson(person)
println(json)
(json \ "id").as[Int] should be (1)
(json \ "first_name").get should be (JsNull)
(json \ "last_name").get should be (JsNull)
}
}
This results in the following compiler error:
PersonSpec.scala:19: No Json formatter found for type Option[String]. Try to implement an implicit Format for this type.
[error] (__ \ "first_name").format[Option[String]] and
[error] ^
These are some of the things I have tried:
Replacing (__ \ "first_name").format[Option[String]] with (__ \ "first_name").formatNullable[String] makes the compiler happy, but the test fails (""java.util.NoSuchElementException: None.get"") with the following output (from println(json))
{"id":1}
This confirms with formatNullable's behavior (don't render None valued fields).
Next, I replaced the format with a writes. Like so:
object Person {
implicit lazy val personWrite = (
(__ \ "id").write[Int] and
(__ \ "first_name").write[Option[String]] and
(__ \ "last_name").write[Option[String]]
)(unlift(Person.unapply))
}
Now, the compiler is happy and the test passes.
But I now need to implement a separate Reads. If I could, I would rather not as it violates DRY principle.
What am I doing wrong and when write[Option[...]] works perfectly why not format[Option[...]]?
Adding this code so that it is implicit-visible from your PersonFormat will make it work.
implicit def optionFormat[T: Format]: Format[Option[T]] = new Format[Option[T]]{
override def reads(json: JsValue): JsResult[Option[T]] = json.validateOpt[T]
override def writes(o: Option[T]): JsValue = o match {
case Some(t) ⇒ implicitly[Writes[T]].writes(t)
case None ⇒ JsNull
}
}
I think that in play it is assumed that option-valued fields should be treated optional at all, hence the behaviour you observed with formatNullable.
You can use:
(__ \ "first_name").formatNullable[String]

How to use specific apply method in implicit Json `reads` from Scala

I have a class that takes a few optional Enumeration types in its constructor:
case class GPMedia(id: Option[GPID], created: Option[DateTime], active: Option[Boolean], data: Array[Byte], mimeType: Option[GPMediaType.Type], encoding: Option[GPEncoder.Codec], compression: Option[GPCompressor.Type])
I've been struggling to create an implicit Json reads method that works. I keep ending up with errors such as:
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:57: overloaded method value apply with alternatives:
etc...
What I'm trying to do is translate the inbound Json strings, turning them into the right kind of Option instance (eg., a MIME type "image/png" in the Json would turn into Option(GPMediaType(v)). The GPMediaType constructor will map the string, and return a correct value (one of which is GPMediaType.Unknown).
Here's the implicit reads that I've worked up so far, implemented on the GPMedia class' companion object...
case object GPMedia extends GPRequestLogging {
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
)(GPMedia.apply _)
}
This works, but when I try to add other apply() methods, it all goes to heck. How can I apply a specific apply method in the Json reads implementation? For example, when I add this apply method:
def apply(data: Array[Byte]) = new GPMedia(None, None, None, data, None, None, None)
I end up with:
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:60: ambiguous reference to overloaded definition,
[error] both method apply in object GPMedia of type (id: Option[models.GPID], created: Option[org.joda.time.DateTime], active: Option[Boolean], data: Array[Byte], mimeType: Option[utility.GPMediaType.Type], encoding: Option[utility.GPEncoder.Codec], compression: Option[utility.GPCompressor.Type])utility.GPMedia
[error] and method apply in object GPMedia of type (data: Array[Byte])utility.GPMedia
[error] match expected type ?
[error] )(GPMedia.apply _)
I've tried a few different approaches, such as (GPMedia.apply(...)) but I can't seem to get the parameters right.
I'm new to the whole Json implicit reader/writer and Json decoding syntax. Clearly I'm missing something here...
Edit
Here's another example, regarding my attempt to call a specific apply method:
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
)(v => GPMedia.apply(v.id, v.created, v.active, v.data, v.mimeType, v.encoding, v.compression))
This results in:
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:60: type mismatch;
[error] found : utility.GPMedia
[error] required: (Option[models.GPID], Option[org.joda.time.DateTime], Option[Boolean], Array[Byte], Option[utility.GPMediaType.Value], Option[utility.GPEncoder.Value], Option[utility.GPCompressor.Type])
[error] (which expands to) (Option[models.GPID], Option[org.joda.time.DateTime], Option[Boolean], Array[Byte], Option[utility.GPMediaType.Value], Option[utility.GPEncoder.Value], Option[utility.GPCompressor.Value])
[error] )(v => GPMedia.apply(v.id, v.created, v.active, v.data, v.mimeType, v.encoding, v.compression))
[error] ^
It is possible.
But You need to specify all the parameters required for the overloaded apply method using _: ParameterType like I have done below and it will work.
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
) (
v => GPMedia.apply(
_: GPID,
_: DateTime,
_: Boolean,
_: Array[Byte],
_: Option[GPMediaType],
_: Option[GPEncoder],
_: Option[GPCompressor]
)
)
This is not possible. The compiler does not know which apply method to use. This is just one of the caveats of using overloaded methods. The only way you can make this like "nice" is to rename the methods, or alias the overloaded apply methods with different names and use those.
Your second attempt does not work because the compiler is expecting a function with a signature similar to apply, like:
(Option[GPID], Option[DateTime], Option[Boolean], Array[Byte], Option[String], Option[String], Option[String]) => GPMedia
But you're trying to use:
GPMedia => GPMedia
Which doesn't work, because we don't yet have a GPMedia object, just the tupled fields. It would look more like:
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
).tupled.map(v => GPMedia.apply(v._1, v._2, v._3, v._4, v._5, v._6, v._7))
Which does not look good. Usually we can make it look better like this:
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
).tupled.map(v => GPMedia.apply _ tupled v)
Except that you will end up with the same problem that you started with, because the compiler will not be able to choose the correct apply method. So you really have no choice but to rename or make things ugly.

Error on writing a json formatter in a Play application

i'm writing a Play 2.3.2 application in Scala.
I've write a class that represents the correlation between a category and an attribute in my system.
The class is declared like the following:
case class Correlation(val category: String, val attribute: String,
val value: Double, val weight: Double)
Now i'm writing a json formatter but i'm getting some errors.
The Formatter is write like the following:
object CorrelationFormatters {
implicit val storageFormatter: Format[Correlation] = {
val correlationReads: Reads[Correlation] = (
(__ "category").read[String] and
(__ "attribute").read[String] and
(__ "value").read[Double] and
(__ "weight").read[Double]
)(Correlation.apply _)
val correlationWrites: Writes[Correlation] = (
(__ "category").write[String] and
(__ "attribute").write[String] and
(__ "value").write[Double] and
(__ "weight").write[Double]
)(unlift(Correlation.unapply _))
}
}
But i'm getting the following compiler errors:
error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/formatters/json/CorrelationFormatters.scala:19: ')' expected but string literal found.
[error] (__ "category").read[String] and
[error] ^
[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/formatters/json/CorrelationFormatters.scala:31: ')' expected but '}' found.
[error] }
[error] ^
[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/formatters/json/CorrelationFormatters.scala:19: ')' expected but string literal found.
[error] (__ "category").read[String] and
[error] ^
[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/formatters/json/CorrelationFormatters.scala:31: ')' expected but '}' found.
[error] }
[error] ^
[error] two errors found
What's wrong??
I've used the ScalaJsonCombinators tutorial like example, but i can't find out where i'm wrong.
Try with:
object Correlation {
implicit val jsonModelReads = Json.reads[Correlation]
implicit val jsonModelWrites = Json.writes[Correlation]
implicit val jsonModelFormat = Json.format[Correlation]
}
And then import Correlation._
Very simple fix:
(__ "category").read[String]
should be
(__ \ "category").read[String]
And for all the other lines as well..

setting default values with Play! Json Combinators

I'm using play!'s json combinators to validate, read and write JSON. Is it possible to specify default values in reads or writes if they are not set?
validation of json is done like this (where json is a JsValue):
json.validate[Pricing]
My code is:
case class Pricing(
_id: ObjectId = new ObjectId,
description: String,
timeUnit: TimeUnit.Value,
amount: Double = 0.0) {
#Persist val _version = 1
}
my reads and writes:
implicit val pricingReads: Reads[Pricing] = (
(__ \ "_id").read[ObjectId] and
(__ \ "description").read[String] and
(__ \ "timeUnit").read[TimeUnit.Value] and
(__ \ "amount").read[Double]
)(Pricing.apply _)
implicit val pricingWrites: Writes[Pricing] = (
(__ \ "_id").write[ObjectId] and
(__ \ "description").write[String] and
(__ \ "timeUnit").write[TimeUnit.Value] and
(__ \ "amount").write[Double]
)(unlift(Pricing.unapply))
so if I would recieve a Json like:
{"description": "some text", "timeUnit": "MONTH"}
I get errors, that fields _id and amount are missing. Is there any possiblity to set the default values without adding it directy to the JsValue?
Thanks in advance!
I'd rather use Options:
case class Pricing(
_id: Option[ObjectId],
description: String,
timeUnit: TimeUnit.Value,
amount: Option[Double]) {
#Persist val _version = 1
}
and replace your pricingReads with this:
implicit val pricingReads: Reads[Pricing] = (
(__ \ "_id").readNullable[ObjectId] and
(__ \ "description").read[String] and
(__ \ "timeUnit").read[TimeUnit.Value] and
(__ \ "amount").readNullable[Double]
)(Pricing.apply _)
Then your code will work on missing fields and yo will be able to do this:
_id.getOrElse(new ObjectId)

Play Framework 2.1.1 Json Writes nested objects

I've been trying to get my head around this all afternoon to no avail, Play 2.1.1 overhauled how the Json reads and writes work.
Basically I have a wrapper object that looks like:
case class CombinedUser(user: SocialUser, userdetails: UserDetails)
as you can see it contains 2 classes that I want to serialize and deserialize to json.
But I don't understand how I can get and set the fields in the subclasses using the new design.
For example
implicit val combinedUser2Reads = (
(__ \ "email").read[String] and
(__ \ "providerid").read[String] and
(__ \ "firstname").read[String] and
(__ \ "lastname").read[String] and
(__ \ "fullname").read[String] and
(__ \ "avatarurl").read[String] and
(__ \ "address1").read[String] and
(__ \ "address2").read[String] and
(__ \ "address3").read[String] and
(__ \ "city").read[String] and
(__ \ "country").read[String] and
(__ \ "phone").read[String] and
(__ \ "publickey").as[String]
)(CombinedUser2.apply _)
I want a json blob that has most of the subclasses fields, all strings.
Because it uses the apply stuff, I don't see how I can create the subclasses before the mapping.
Any help or guidance is much appreciated.
Thanks
Tom
You can do it this way:
case class SocialUser(firstName: String, lastName: String)
case class UserDetails(avatarUrl: String, phone: String)
case class CombinedUser(user: SocialUser, userDetails: UserDetails)
implicit val combinedUserReads: Reads[CombinedUser] = (
(__ \ "user").read((
(__ \ "firstName").read[String] and
(__ \ "lastName").read[String]
)(SocialUser)) and
(__ \ "userDetails").read((
(__ \ "avatarUrl").read[String] and
(__ \ "phone").read[String]
)(UserDetails))
)(CombinedUser)
However its better to create separated Reads:
implicit val socialUserReads = (
(__ \ "firstName").read[String] and
(__ \ "lastName").read[String]
)(SocialUser)
implicit val userDetailsReads = (
(__ \ "avatarUrl").read[String] and
(__ \ "phone").read[String]
)(UserDetails)
implicit val combinedUserReads: Reads[CombinedUser] = (
(__ \ "user").read[SocialUser] and
(__ \ "userDetails").read[UserDetails]
)(CombinedUser)
Edit: for simple case classes, its possible to do:
implicit val socialUserReads = Json.format[SocialUser]
implicit val userDetailsReads = Json.format[UserDetails]
implicit val combinedUserReads = Json.format[CombinedUser]
Here is quite comprehensive introduction to JSON Reads and more.
How about partial objects? If I don't want to fill in every field in
the constructor, can I pass empties or do I overload the constructor
or similar?
Use Option:
case class CombinedUser(user: SocialUser, userDetails: Option[UserDetails])
//reads
implicit val combinedUserReads: Reads[CombinedUser] = (
(__ \ "user").read[SocialUser] and
(__ \ "userDetails").readOpt[UserDetails]
)(CombinedUser)
//writes
implicit val combinedUserWrites: Writes[CombinedUser] = (
//socialUserWrites and userDetailsWrites must be in scope
(__ \ "user").write[SocialUser] and
(__ \ "userDetails").write[Option[UserDetails]]
)(unlift(CombinedUser.unapply))
val json = Json.obj(
"user" -> Json.obj(
"firstName" -> "Homer",
"lastName" -> "Simpson"
)
)
Json.fromJson[CombinedUser](json)
//JsSuccess(CombinedUser(SocialUser(Homer,Simpson),None),)