Search code examples
scalaplayframeworkplayframework-2.0play-json

Create Single sub Field from multiple Json Fields and apply to resulting Object using Play-json


I am trying to use a play-json reads to convert the following Json into a resulting case class. However, I am stuck on the syntax to convert the longitude and Latitude json values into a Point object meanwhile converting the rest of the json values into the same resulting BusinessInput object. Is this syntactically possible?

case class BusinessInput(userId: String, name: String, location: Point, address: Option[String], phonenumber: Option[String], email: Option[String])

object BusinessInput {

  implicit val BusinessInputReads: Reads[BusinessInput] = (
    (__ \ "userId").read[String] and
    (__ \ "location" \ "latitude").read[Double] and
      (__ \ "location" \ "longitude").read[Double]
    )(latitude: Double, longitude: Double) => new GeometryFactory().createPoint(new Coordinate(latitude, longitude))

Solution

  • Fundamentally, a Reads[T] just requires a function that turns a tuple into an instance of T. You can therefore write one for your Point class, given location JSON object, like so:

    implicit val pointReads: Reads[Point] = (
      (__ \ "latitude").read[Double] and
      (__ \ "longitude").read[Double]      
    )((lat, lng) => new GeometryFactory().createPoint(new Coordinate(lat, lng))
    

    and then combine that with the rest of your data for the BusinessInput class:

    implicit val BusinessInputReads: Reads[BusinessInput] = (
      (__ \ "userId").read[String] and
      (__ \ "name").read[String] and
      (__ \ "location").read[Point] and
      (__ \ "address").readNullable[String] and
      (__ \ "phonenumber").readNullable[String] and
      (__ \ "email").readNullable[String]
    )(BusinessInput.apply _)
    

    In the second case we use the BusinessInput classes apply method as a short cut, but you could just as easily take a tuple of (userId, name, point) and create one with the optional fields left out.

    If you don't want to make a Point reads separately, just combine them using the same principals:

    implicit val BusinessInputReads: Reads[BusinessInput] = (
      (__ \ "userId").read[String] and
      (__ \ "name").read[String] and
      (__ \ "location").read[Point]((
        (__ \ "latitude").read[Double] and
        (__ \ "longitude").read[Double]
      )((lat, lng) => new GeometryFactory().createPoint(new Coordinate(lat, lng)))) and
      (__ \ "address").readNullable[String] and
      (__ \ "phonenumber").readNullable[String] and
      (__ \ "email").readNullable[String]
    )(BusinessInput.apply _)