1

I am looking for a way to define a Reads which allows me to map a JSON containing the following structure:

{
    "offers": [
        [
            {
                "id": "1234",
                (etc.)
            }
        ]
    ]
}

to model such case class TransportOffer(offers: List[Offer])

Unfortunately I haven't been able to do this yet. This is my code:

implicit val transportOfferReads: Reads[TransportOffer] = (
    (JsPath \ "offers").read[List[List[Offer]]].flatMap(r => r.flatten)
    )(TransportOffer.apply _)

In this case the flattening is not possible, as flatMap expects another Reads. How would I wrap the flattened List into another Reads? Or is there a simpler way?

cchantep
  • 9,118
  • 3
  • 30
  • 41
Remo
  • 1,112
  • 2
  • 12
  • 25

1 Answers1

1

I'll present 3 options:

  1. Flattening in a short reads:
case class Offer(id: String)

object Offer {
  implicit val format: OFormat[Offer] = Json.format[Offer]
}

case class TransportOffer(offers: List[Offer])

object TransportOffer {
  implicit val transportOfferReads: Reads[TransportOffer] =
    (JsPath \ "offers").read[List[List[Offer]]].map(x => TransportOffer(x.flatten))
}

Then calling:

Json.parse(jsonString).validate[TransportOffer].foreach(println)

outputs:

TransportOffer(List(Offer(1234)))

Code run at Scastie

  1. Explicitly writing Reads:
implicit val transportOfferReads: Reads[TransportOffer] = (json: JsValue) => {
  json \ "offers" match {
    case JsUndefined() =>
      JsError("offers undefined")
    case JsDefined(value) =>
      value.validate[List[List[Offer]]].map(x => TransportOffer(x.flatten))
  }

Code run at Scastie.

  1. First transform the json, into the model you'd like. For that define a transformer:
val jsonTransformer = (__ \ "offers").json
  .update(__.read[JsArray].map{o => {
    JsArray(o.value.flatMap(_.asOpt[JsArray].map(_.value)).flatten)
  }})

Then, assuming we have the case classes and their formatters:

case class Offer(id: String)

object Offer {
  implicit val format: OFormat[Offer] = Json.format[Offer]
}

case class TransportOffer(offers: List[Offer])

object TransportOffer {
  implicit val format: OFormat[TransportOffer] = Json.format[TransportOffer]
}

We can call:

Json.parse(jsonString).transform(jsonTransformer) match {
  case JsSuccess(value, _) =>
    value.validate[TransportOffer].foreach(println)
  case JsError(errors) =>
    println(errors)
    ???
}

Output is:

TransportOffer(List(Offer(1234)))

Code run at Scastie.

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35