6

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, Position is not mandatory in Person case class.

I was trying 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 I have no idea how to deal with the Option[Position] object: the intention would be to instantiate a Some(Position(lat,lon)) if both 'lat' and 'lon' are specified and not null, otherwise instantiate None.

How would you deal with that?

juco
  • 6,331
  • 3
  • 25
  • 42
Max
  • 2,508
  • 3
  • 26
  • 44

2 Answers2

10

I'm pretty sure there's a better way of doing what you want than what I'm going to post, but it's late and I can't figure it out now. I'm assuming that simply changing the JSON structure you're consuming isn't an option here.

You can supply a builder function that takes two optional doubles for lat/lon and yields a position if they're both 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 to be valid JSON you need to quote the data keys.

Community
  • 1
  • 1
Mikesname
  • 8,781
  • 2
  • 44
  • 57
4

Your javascript object should match the structure of your case classes. Position will need to have a json reader as well.

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 either of the fields of Position are null/missing in the json object, it will be parsed as None. So, jsonStr.as[Person] = Person("FirstName", "LastName", None)

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Thanks for your tip, but unfortunately I cannot change the JSON structure, that's why I have no idea how to solve this. – Max Oct 22 '13 at 19:14