I have a case class
case class Test(a: String, b: Option[Double])
object TestSerializer extends CustomSerializer[Test] (format => ({
case jv: JValue =>
val a = (jv \ "a").extract[String]
val b = (jv \ "b").extractOpt
Test(a, b)
},{
case tst: Test =>
tst.b match {
case Some(x) => ("a" -> "test") ~ ("b" -> x)
case None => ("a" -> "test") ~ ("b" -> "NA")
}
}))
When b is available, the result I get is: {a: "test", b: 1.0}
When b = None, the result I get is: {a: "test"}
The second result throws an exception in the first partial function since it cannot find b.
How can I ensure my code does not fail and instead treat the missing b value of json as None?
I am using json4s 3.2.10 and not 3.2.11 so I cannot use the preserveEmpty fields option.
Related
My case class has 30 fields. For simplicity, I use 4 fields,
case class Person(id: Long, name: String, age: Int, sex: Sex)
val personFormat1: OFormat[(Long, String)] = ((__ \ "id").format[Long] ~ (__ \ "name").format[String]).tupled
val personFormat2: OFormat[(Int, Sex)] = ((__ \ "age").format[Int] ~ (__ \ "sex").format[Sex]).tupled
implicit val personFormat: Format[Person] = (personFormat1 ~ personFormat2)({
case ((id, name), (age, sex)) => new Person(id, name, age, sex)
}, (person: Format) => ((person.id, person.name), (person.age, person.sex)))
But even after writing formatter with format1 as a group of 22 fields and format2 as a group of 8 fields, I get error when am trying to parse the json of this case class.
Error is
No Json serializer as JsObject found for type Person. Try to implement an implicit OWrites or OFormat for this type.
How to write implicit Owrites or OFormat? or how to fix this issue?
I use Play-Json extensions library for working with JSON with more than 22 fields: https://github.com/xdotai/play-json-extensions
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.8.0"
You need to an implicit writer to do this. Something like this
implicit val person= Json.format[Person]
Also, if you are using custom data types, like for your case Sex you need to specify a reader and writer. You do not need to do this for primitive types like Int, Long, String Etc.
def enumReads[T <: Enum[T]](mkEnum: String => T) = new Reads[T] {
override def reads(json: JsValue): JsResult[T] =
json match {
case JsString(s) => try {
JsSuccess(mkEnum(s))
}
catch {
case e: IllegalArgumentException =>
JsError("Not a valid enum value: " + s)
}
case v => JsError("Can't convert to enum: " + v)
}
}
implicit val enumWrites = new Writes[Enum[_]] {
def writes(e: Enum[_]) = JsString(e.toString)
}
implicit val sex = enumReads(Sex.valueOf)
Also, upgrade to scala 2.11 or later to avoid the limitation of 22 fields in case class. For more info see here: How to get around the Scala case class limit of 22 fields?
I found this using google, and this worked greatly for me
// https://mvnrepository.com/artifact/com.chuusai/shapeless_2.11
libraryDependencies += "com.chuusai" % "shapeless_2.11" % "2.3.2"
// https://mvnrepository.com/artifact/org.julienrf/play-json-derived-codecs_2.11
libraryDependencies += "org.julienrf" % "play-json-derived-codecs_2.11" % "3.2"
I have a Json object, which is an output of Json.parse, which is of format
{ "main_data" : [ {"a" : 1, "b" :2} , {"a" : 3, "b" : 4}, {"a" : 5, "b" : 6}]}.
I want to create a list out of this Json object with the values of all the "a" key. So in the above example it will be [1,3,5]. As I am new to functional programming the first thing came to my mind was to write a For loop and traverse through the Json object to get the list.
But I was wondering is there a Functional/Scala way to do the above, using Map or flatMap ?
import play.api.libs.json._
val parsed = Json.parse("""{"main_data":[{"a":1,"b":2},{"a":3,"b":4},{"a":5,"b":6}]}""")
val keys = parsed \\ "a"
val result = keys.flatMap(_.asOpt[Int]) // List(1, 3, 5)
You can use something like this
val json = Json.parse(yourJsonString)
val aKeys = json \\ "a"
// aKeys: Seq[play.api.libs.json.JsValue] = List(1, 3, 5)
// If you need integers instead of JsValues, just use map
val integersList = aKeys.map(x => x.as[Int])
There are plenty of choices. Let's say you have:
import play.api.libs.json._
val txt =
"""
|{ "main_data" : [ {"a" : 1, "b" :2} , {"a" : 3, "b" : 4}, {"a" : 5, "b" : 6}]}
""".stripMargin
val json = Json.parse(txt)
First approach if you are interested only in a:
(json \ "main_data")
.as[List[Map[String, Int]]]
.flatten
.foldLeft(List[Int]()){
case (acc, ("a", i)) => acc :+ i
case (acc, _) => acc
}
The second more general:
(json \ "main_data")
.as[List[Map[String, Int]]]
.flatten
.groupBy(_._1)
.map {
case (k, list) => k -> list.map(_._2)
}
.get("a")
And the result is:
res0: Option[List[Int]] = Some(List(1, 3, 5))
{
"location":{
"residents":[{
"renting":[{
"name":"John Doe"
"pets":"2"
},{
"name":"Jane Smith"
"pets":"2"
}]
}]
}
}
I can successfully traverse location with this -
val json = ...
val rentReads = (__ \ 'location).read[String]
val rentResult = json.validate[String](rentReads)
rentResult match {
case s: JsSuccess[String] => Ok(s.get)
case e: JsError => Ok("Errors: " + JsError.toFlatJson(e).toString())
}
Based on the documentation, I should be able to do something like this -
val skillReads = ((__ \ 'location) \ 'residents)(0).read[String]
but it results in the following error -
Errors: {"obj.VariationResultsWrapper.VariationResultSets[0]":[{"msg":"error.path.missing","args":[]}]}
At this point I'm just trying to understand how to return values from "renting" only. Eventually, I would like to map that result to a case class.
If your eventual goal is to parse this into case classes, just define those case classes and let Play do the heavy lifting.
case class Renting(name: String, pets: String)
case class Resident(renting: List[Renting])
case class Location(residents: List[Resident])
implicit val rentingFormat = Json.format[Renting]
implicit val residentFormat = Json.format[Resident]
implicit val locationFormat = Json.format[Location]
(json \ "location").validate[Location]
res1: play.api.libs.json.JsResult[Location] = JsSuccess(Location(List(Resident(List(Renting(John Doe,2), Renting(Jane Smith,2))))),/residents)
I'm using play-json's macros to define implicit Writes for serializing JSON. However, it seems like by default play-json omits fields for which Option fields are set to None. Is there a way to change the default so that it outputs null instead? I know this is possible if I define my own Writes definition, but I'm interested in doing it via macros to reduce boilerplate code.
Example
case class Person(name: String, address: Option[String])
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))
// Outputs: {"name":"John Smith"}
// Instead want to output: {"name":"John Smith", "address": null}
The Json.writes macro generates a writeNullable[T] for optional fields. Like you know (or not), writeNullable[T] omits the field if the value is None, whereas write[Option[T]] generates a null field.
Defining a custom writer is the only option you have to get this behavior.
(
(__ \ 'name).write[String] and
(__ \ 'address).write[Option[String]]
)(unlift(Person.unapply _))
You can use a custom implicit JsonConfiguration, see Customize the macro to output null
implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))
Not a real solution for you situation. But slightly better than having to manually write the writes
I created a helper class that can "ensure" fields.
implicit class WritesOps[A](val self: Writes[A]) extends AnyVal {
def ensureField(fieldName: String, path: JsPath = __, value: JsValue = JsNull): Writes[A] = {
val update = path.json.update(
__.read[JsObject].map( o => if(o.keys.contains(fieldName)) o else o ++ Json.obj(fieldName -> value))
)
self.transform(js => js.validate(update) match {
case JsSuccess(v,_) => v
case err: JsError => throw new JsResultException(err.errors)
})
}
def ensureFields(fieldNames: String*)(value: JsValue = JsNull, path: JsPath = __): Writes[A] =
fieldNames.foldLeft(self)((w, fn) => w.ensureField(fn, path, value))
}
so that you can write
Json.writes[Person].ensureFields("address")()
Similar answer to above, but another syntax for this:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> p.address,
)
}
This is simple:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> noneToString(p.address),
)
}
def optToString[T](opt: Option[T]) =
if (opt.isDefined) opt.get.toString else "null"
You can define something like this :
implicit class JsPathExtended(path: JsPath) {
def writeJsonOption[T](implicit w: Writes[T]): OWrites[Option[T]] = OWrites[Option[T]] { option =>
option.map(value =>
JsPath.createObj(path -> w.writes(value))
).getOrElse(JsPath.createObj(path -> JsNull))
}
}
And if you are using play framework :
implicit val totoWrites: Writes[Toto] = (
(JsPath \ "titre").write[String] and
(JsPath \ "option").writeJsonOption[String] and
(JsPath \ "descriptionPoste").writeNullable[String]
) (unlift(Toto.unapply))
implicit val totoReads: Reads[Toto] = (
(JsPath \ "titre").read[String] and
(JsPath \ "option").readNullable[String] and
(JsPath \ "descriptionPoste").readNullable[String]
) (Toto.apply _)
You may wrap your option and redefine serialization behavior:
case class MyOption[T](o: Option[T])
implicit def myOptWrites[T: Writes] = Writes[MyOption[T]](_.o.map(Json.toJson).getOrElse(JsNull))
CAVEATS:
1)this approach is good only for case classes used solely as a serialization protocol definition. If you are reusing some data model classes in controllers - define custom serializers for them not to pollute the model.
2)(related only to Option) if you use the same class for writes and reads. Play will require the wrapped fields to be present (possibly null) during deserialization.
P.S.: Failed to do the same with type tagging. Compiler error is like
No instance of play.api.libs.json.Writes is available for tag.<refinement> (given the required writes were explicitly defined).
Looks like Play's macro fault.
This question is based on the following example, which is an attempt to deserialize a case class Node[Bird] from JSON.
import play.api.libs.json._
import play.api.libs.functional.syntax._
object ValidationTest {
case class Node[T](_id: Int, thing: T)
case class Bird(name: String, color: String)
// ERROR -- No apply function found matching unapply parameters
implicit val birdNodeFormat = Json.format[Node[Bird]]
val birdNodesJson: JsValue = Json.arr(
Json.obj(
"_id" -> 0,
"thing" -> Json.obj(
"name" -> "Cardinal",
"color" -> "Red"
)
),
Json.obj(
"_id" -> 1,
"thing" -> Json.obj(
"name" -> "Bluejay",
"color" -> "Blue"
)
)
)
val birdNodesResult = birdNodesJson.validate[Seq[Node[Bird]]]
}
In the preceding example, Scala is unable to resolve the proper apply/unapply functions for Node[Bird] for the format macro.
// ERROR -- No apply function found matching unapply parameters
implicit val birdNodeFormat = Json.format[Node[Bird]]
However, there is no problem with using a non-generic case class such as BirdNode.
case class BirdNode(_id: Int, thing: Bird)
// WORKS
implicit val birdNodeFormat = Json.format[BirdNode]
...
// WORKS
val birdNodesResult = birdNodesJson.validate[Seq[BirdNode]]
What is the best way to serialize/deserialize something like a Node[Bird] to/from JSON in Play 2.2?
You might have to define the format for Node[T] without using the macro, but this works:
implicit val birdFormat: Format[Bird] = Json.format[Bird]
implicit def nodeFormat[T : Format]: Format[Node[T]] =
((__ \ "_id").format[Int] ~
(__ \ "thing").format[T]
)(Node.apply, unlift(Node.unapply))