So I have a structure in json looking like this:
{
"lst": [
{"name": "foo"},
{"name": "bar"}
]
}
I'm having the hardest time converting this to a list of case classes. I'm sure I'm missing something completely obvious...
I have tried this:
case class Person(name: String)
implicit val personReads: Reads[Email] = (__ \\ "name").read[String](Person)
// endpoint
def person = Action { request =>
val person = request.body.asJson.get.as[Seq[Person]]
}
which doesn't compile since read doesn't return a FunctionBuilder which means I can't apply the path to Person
Adding a new parameter does compile (changing the json and case class accordingly):
case class Person(name: String, age: String)
implicit val personReads: Reads[Email] = (
(__ \\ "name").read[String]) and
(__ \\ "age").read[Int](Person)
but throws an exception Execution exception[[JsResultException: JsResultException(errors:List((,List(ValidationError(List(error.expected.jsarray),WrappedArray())))))]] supposedly because it expects a list.
So I tried adding this:
implicit val personsReads: Reads[Seq[Person]] = (__ \ "lst").read[Seq[Person]]
which then throws a NullPointer.
In the end I just want a Seq[Person].
Can anyone point me in the right direction, I'm completely lost to what I'm expected to do here...
You can do the following instead of giving reads and writes explicitly.
import play.api.json.Json
case class Person(name: String)
object Person {
implicit val personFormat = Json.format[Person]
}
case class Persons(lst: List[Person])
object Persons {
implicit val personsFormat = Json.format[Persons]
}
Now take the json string lets say jsonStr
Json.parse(jsonStr).validate[Persons] match {
case JsSuccess(persons, _) => println(persons)
case JsError(_) => println("parsing failed")
}
Scala REPL
scala> import play.api.libs.json._
import play.api.libs.json._
scala> val str = """{
| "lst": [
| {"name": "foo"},
| {"name": "bar"}
| ]
| }""".stripMargin
str: String =
{
"lst": [
{"name": "foo"},
{"name": "bar"}
]
}
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class Person(name: String)
object Person {
implicit val personFormat = Json.format[Person]
}
case class Persons(lst: List[Person])
object Persons {
implicit val personsFormat = Json.format[Persons]
}
// Exiting paste mode, now interpreting.
defined class Person
defined object Person
defined class Persons
defined object Persons
scala> val jsonStr = str
jsonStr: String =
{
"lst": [
{"name": "foo"},
{"name": "bar"}
]
}
scala> :paste
// Entering paste mode (ctrl-D to finish)
Json.parse(jsonStr).validate[Persons] match {
case JsSuccess(persons, _) => println(persons)
case JsError(_) => println("parsing failed")
}
// Exiting paste mode, now interpreting.
Persons(List(Person(foo), Person(bar)))
Now when you change your Person case class and add age field.
case class Person(name: String, age: Int)
object Person {
implicit val personFormat = Json.format[Person]
}
Ensure that the json you are trying to parse contains both name and age. If you have only name then you will get parsing error.
Related
Here is my json:
{
"stringField" : "whatever",
"nestedObject": { "someProperty": "someValue"}
}
I want to map it to
case class MyClass(stringField: String, nestedObject:String)
nestedObject should not be deserialized, I want json4s to leave it as string.
resulting instance shouldBe:
val instance = MyClass(stringField="whatever", nestedObject= """ { "someProperty": "someValue"} """)
Don't understand how to do it in json4s.
You can define a custom serializer:
case object MyClassSerializer extends CustomSerializer[MyClass](f => ( {
case jsonObj =>
implicit val format = org.json4s.DefaultFormats
val stringField = (jsonObj \ "stringField").extract[String]
val nestedObject = compact(render(jsonObj \ "nestedObject"))
MyClass(stringField, nestedObject)
}, {
case myClass: MyClass =>
("stringField" -> myClass.stringField) ~
("nestedObject" -> myClass.nestedObject)
}
))
Then add it to the default formatter:
implicit val format = org.json4s.DefaultFormats + MyClassSerializer
println(parse(jsonString).extract[MyClass])
will output:
MyClass(whatever,{"someProperty":"someValue"})
Code run at Scastie
How can I use the play json OFormat macro to get a list of option?
val text = """[{"name": "John", "age": 30}, null, {"name": "Steve", "age": 34}]"""
import play.api.libs.json.Json
case class Person(name: String, age: Int)
implicit val personFormat = Json.format[Person]
val data = Json.parse(text).validate[List[Option[Person]]]
// Error: No Json deserializer found for type List[Option[Person]]. Try to implement an implicit Reads or Format for this type.
I am doing as follows, as a workaround:
val data = Json.parse(text).as[Array[JsValue]].toList.map {
case JsNull => None
case x => Some(x.validate[Person].get)
}
println(data)
// List(Some(Person(John,30)), None, Some(Person(Steve,34)))
How do I achieve the same without this workaround, using only the OFormat macro?
Not sure that it is possible directly, but could be done so for example (used this answer):
val text = """[{"name": "John", "age": 30}, null, {"name": "Steve", "age": 34}]"""
import play.api.libs.json._
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]) = o match {
case Some(t) ⇒ implicitly[Writes[T]].writes(t)
case None ⇒ JsNull
}
}
case class Person(name: String, age: Int)
implicit val personFormat= {
implicit val f = Json.format[Person]
implicitly[Format[Option[Person]]]
}
val data = Json.parse(text).validate[List[Option[Person]]] // JsSuccess(List(Some(Person(John,30)), None, Some(Person(Steve,34))),)
Json.toJson(data.get) // [{"name":"John","age":30},null,{"name":"Steve","age":34}]
it just cannot translate by its own Reads[Person] -> Reads[Option[Person]] -> Reads[List[Option[Person]]]. I do help to get Reads[Option[Person]] with helper general method. Probably, analogue method is available in play lib..
I have a list of JsObject like:
[{
"a":"test1",
"b": 2,
"c": 5,
"errors": "Error example"
}]
I would like to get something like this a:List[Option[String]], b: List[Option[Int]] and so on. I need an option since not all the fields are alway present.
My code is:
jsObjList.map(js => {
val a = (js \ "a").asOpt[String]
val b = (js \ "b").asOpt[Int]
val c = (js \ "c").asOpt[Int]
val er= (js \ "errors").asOpt[String]
(a, b, er)
})
I read about unzip and unzip3 but I haven't found a generic function.
P.S. I am using Scala Play for the json parsing
Thanks for your help!
Class to extract values from raw JSON.
case class Foo(a: Option[String], b: Option[Int], c: Option[Int],errors: Option[String])
object Foo {
// Automatically generate json reader and writer for the class Foo
implicit val format = Json.format[Foo]
}
Keeping the implicit value in companion object of Foo will make the Scala to pick up the implicit when required automatically.
Code to parse JSON into list of case class instances
payload.validate[List[Foo]]
Use validateOpt in case you expect any parse error
payload.validateOpt[List[Foo]]
Scala REPL
scala> :paste
// Entering paste mode (ctrl-D to finish)
val str = """
[{
"a":"test1",
"b": 2,
"c": 5,
"errors": "Error example"
}]
"""
// Exiting paste mode, now interpreting.
str: String =
"
[{
"a":"test1",
"b": 2,
"c": 5,
"errors": "Error example"
}]
"
scala> val payload = Json.parse(str)
payload: play.api.libs.json.JsValue = [{"a":"test1","b":2,"c":5,"errors":"Error example"}]
scala> case class Foo(a: Option[String], b: Option[Int], c: Option[Int],errors: Option[String])
defined class Foo
scala> implicit val format = Json.format[Foo]
format: play.api.libs.json.OFormat[Foo] = play.api.libs.json.OFormat$$anon$1#53a0b0a3
scala> payload.validate[List[Foo]]
res5: play.api.libs.json.JsResult[List[Foo]] = JsSuccess(List(Foo(Some(test1),Some(2),Some(5),Some(Error example))),)
You can parse JSON as a Scala case class with a companion object containing a special val called implicit val format = Json.format[*your class*].
Here's an example similar to yours:
import play.api.libs.json.Json
val body =
"""{
| "a":"my string",
| "b": 1,
| "c": 2
|}
""".stripMargin
val body2 =
"""{
| "a":"my string",
| "c": 5
|}
""".stripMargin
case class MyClass(a: Option[String], b: Option[Int], c: Option[Int])
object MyClass {
implicit val format = Json.format[MyClass]
}
Using this, calling Json.parse(body).as[MyClass] gives:
res0: MyClass = MyClass(Some(my string),Some(2),Some(5))
Calling this Json.parse function with missing fields (assuming they are optional), such as Json.parse(body2).as[MyClass] gives:
res1: MyClass = MyClass(Some(my string),None,Some(5))
If one of the missing fields is not Optional, this parse will not work.
Since I'm writing a function to request data from another API in my Scala code, the response Json has the format like this:
"data": {
"attributeName": "some String",
"attributeValue": false,
"attributeSource": "Manual",
"attributeValueLabel": null
},
"data": {
"attributeName": "some String",
"attributeValue": "daily",
"attributeSource": "Manual",
"attributeValueLabel": "Almost Daily"
}
Note that sometimes the type of attributeValue is String value, some other time it's a Boolean value.
So I'm trying to write my own Reads and Writes to read the type dynamically.
case class Data(attributeName: Option[String], attributeValue: Option[String], attributeSource: Option[String], attributeValueLabel: Option[String])
object Data{
implicit val readsData: Reads[Data] = {
new Reads[Data] {
def reads(json: JsValue) = {
val attrValue = (json \ "attributeValue").as[] // How to cast to Boolean some time, but some other time is a String here
......
}
}
}
So as you can see in my comment, I'm stuck at the part to cast the (json \ "attributeValue") to String/Boolean, base on the return type of the API. How can I do this?
You can try to parse it as String first and then as Boolean:
val strO = (json \ "attributeValue").asOpt[String]
val value: Option[String] = strO match {
case str#Some(_) => str
case None => (json \ "attributeValue").asOpt[Boolean].map(_.toString)
}
You can use the .orElse function when you are trying to read an attribute in different ways:
import play.api.libs.json.{JsPath, Json, Reads}
import play.api.libs.functional.syntax._
val json1 =
"""
|{
| "attributeName": "some String",
| "attributeValue": false
|}
""".stripMargin
val json2 =
"""
|{
| "attributeName": "some String",
| "attributeValue": "daily"
|}
""".stripMargin
// I modified you case class to make the example short
case class Data(attributeName: String, attributeValue: String)
object Data {
// No need to define a reads function, just assign the value
implicit val readsData: Reads[Data] = (
(JsPath \ "attributeName").read[String] and
// Try to read String, then fallback to Boolean (which maps into String)
(JsPath \ "attributeValue").read[String].orElse((JsPath \ "attributeValue").read[Boolean].map(_.toString))
)(Data.apply _)
}
println(Json.parse(json1).as[Data])
println(Json.parse(json2).as[Data])
Output:
Data(some String,false)
Data(some String,daily)
I have a case class Foo(bars: List[Bar]) who is rendered as json via Json inception as an object with an array :
{"bars": [
{
"key: "4587-der",
"value": "something"
}
]
}
But I want to render the bars: List[Bar] as a "map" where Bar.key is used as key :
{"bars":{
"4587-der": {
"value": "something"
}
}
}
How can I obtains that without modifying my case class Foo ?
Thanks a lot
You can define a Writes for Bar by extending Writes[Bar] and implementing a writes method for it:
case class Bar(key: String, value: String)
implicit object BarWrites extends Writes[Bar] {
def writes(bar: Bar): JsValue = Json.obj(
bar.key -> Json.obj("value" -> bar.value)
)
}
scala> Json.stringify(Json.toJson(Bar("4587-der", "something")))
res0: String = {"4587-der":{"value":"something"}}
For those that may be interested, here is a (somewhat) crude implementation of Reads[Bar]:
implicit object BarReads extends Reads[Bar] {
def reads(js: JsValue): JsResult[Bar] = js match {
case JsObject(Seq((key, JsObject(Seq(("value", JsString(value))))))) => JsSuccess(Bar(key, value))
case _ => JsError(Seq())
}
}
scala> Json.parse(""" [{"4587-der":{"value": "something"}}] """).validate[List[Bar]]
res11: play.api.libs.json.JsResult[List[Bar]] = JsSuccess(List(Bar(4587-der,something)),)
Edit, since the OP wants the Bars merged into an object rather than an array:
You'll also have to define a special Writes[List[Bar]] as well:
implicit object BarListWrites extends Writes[List[Bar]] {
def writes(bars: List[Bar]): JsValue =
bars.map(Json.toJson(_).as[JsObject]).foldLeft(JsObject(Nil))(_ ++ _)
}
scala> val list = List(Bar("4587-der", "something"), Bar("1234-abc", "another"))
list: List[Bar] = List(Bar(4587-der,something), Bar(1234-abc,another))
scala> Json.stringify(Json.toJson(list))
res1: String = {"4587-der":{"value":"something"},"1234-abc":{"value":"another"}}