Playback 2.2 JSON Read with combinators: how to work with nested optional objects?

I'm going crazy trying to parse this JSON structure in Play Framework 2.2:

val jsonStr = """{ personFirstName: "FirstName", personLastName: "LastName" positionLat: null, positionLon: null }""" 

I have 2 case classes:

 case class Position( val lat: Double, val lon: Double) case class Person( firstName: String, lastName: String, p: Option[Position] ) 

As you can see, the position is not required for the Person case class.

I tried to get an instance of Person using something like this

 implicit val reader = ( (__ \ 'personFirstName ).read[String] ~ (__ \ 'personLastName ).read[String] ~ ( (__ \ 'positionLat ).read[Double] ~ (__ \ 'positionLon ).read[Double] )(Position) )(Person) 

but I soon realized that I had no idea how to deal with the Option[Position] object: the intention was to create an instance of Some(Position(lat,lon)) if both β€œlat” and β€œlon” are specified, not null, otherwise an instance of None .

How do you handle this?

+6
source share
2 answers

I am sure that there is a better way to do what you want than what I am going to publish, but it's late, and I can’t figure it out now. I assume that simply changing the JSON structure you consume is not an option here.

You can provide a builder function that takes two extra doubles for lat / lon and gives a position if both are present.

 import play.api.libs.functional.syntax._ import play.api.libs.json._ val jsonStr = """{ "personFirstName": "FirstName", "personLastName": "LastName", "positionLat": null, "positionLon": null }""" case class Position(lat: Double, lon: Double) case class Person( firstName: String, lastName: String, p: Option[Position] ) object Person { implicit val reader = ( (__ \ "personFirstName" ).read[String] and (__ \ "personLastName" ).read[String] and ( (__ \ "positionLat" ).readNullable[Double] and (__ \ "positionLon" ).readNullable[Double] )((latOpt: Option[Double], lonOpt: Option[Double]) => { for { lat <- latOpt ; lon <- lonOpt} yield Position(lat, lon) }) )(Person.apply _) } Json.parse(jsonStr).validate[Person] // yields JsSuccess(Person(FirstName,LastName,None),) 

Also, note that for correct JSON you need to specify data keys .

+10
source

Your javascript object must match the structure of the case classes. Position should also have a json reader.

 val jsonStr = """{ "personFirstName": "FirstName", "personLastName": "LastName", "position":{ "lat": null, "lon": null } }""" case class Person( firstName: String, lastName: String, p: Option[Position] ) object Person { implicit val reader = ( (__ \ 'personFirstName ).read[String] ~ (__ \ 'personLastName ).read[String] ~ (__ \ 'position ).readNullable[Position] )(Person.apply _) } case class Position( val lat: Double, val lon: Double) object Position { implicit val reader = ( (__ \ 'lat ).read[Double] ~ (__ \ 'lon ).read[Double] )(Position.apply _) } 

If any of the Position fields is null / absent in the json object, it will be parsed as None . So jsonStr.as[Person] = Person("FirstName", "LastName", None)

+4
source

Source: https://habr.com/ru/post/956357/


All Articles