简体   繁体   中英

Play Json Recursive Reads

I'm working on consuming an API which exposes an object at multiple layers within it's response. For example for some responses we get back a:

{ 
  "error": { 
    "code": "123", 
    "description": "Description" 
  } 
}

But in other situations it response with:

{
  "data": [
    {
      "message_id": "123",
      "error": {
        "code": "123",
        "description": "Description"
      }
    }
  ]
}

In both cases the error object is identical and in both case I don't actually care about the rest of the payload. I was hoping to use the \\\\ recursive JsPath operator, however the implementation below fails:

case class ErrorMessage(code: String, description: String)
implicit val errorMessageFormat = Json.format[ErrorMessage]

case class ErrorResponse(errors: Seq[ErrorMessage])
implicit val errorResponseFormat: Format[ErrorResponse] = Format(
  (__ \\ "error").read[Seq[ErrorMessage]].map(ErrorResponse),
  (__ \ "errors").write[Seq[ErrorMessage]].contramap((r: ErrorResponse) => r.errors)
)

This gives an error:

JsError(List((//error,List(ValidationError(List(error.expected.jsarray),WrappedArray())))))

I understand why: The (__ \\\\ "error") returns a Seq[JsValue] , where as my read call is expecting a JsArray .

Is there a nice way a round this?

Since the first piece is already a Seq, just map the internal elements as you would map single objects. I'm not very familiar with the framework (I use Json4s myself mostly), but by your description sounds like

ErrorResponse((__ \\ "error").map(_.read[Seq[ErrorMessage]]))

Should be closer to what you want. (__ \\\\ "error") gives you a Seq of JsValues , map does something for every JsValue , read converts a single JsValue to an ErrorMessage and the resulting Seq[ErrorMessage] you pass to the ErrorResponse constructor.

So for anyone trying to do something similar, the below works.

val errorResponseReads = Reads[ErrorResponse] { value: JsValue =>
  val errorsJsArray: JsArray = JsArray(value \\ "error")
  errorsJsArray.validate[Seq[ErrorMessage]].map(ErrorResponse)
}

The format then becomes:

implicit val errorResponseFormat: Format[ErrorResponse] = Format(
  errorResponseReads,
  (__ \ "errors").write[Seq[ErrorMessage]].contramap((r: ErrorResponse) => r.errors)
)

Basically you need to define Reads that you use explicitly. In the reads you can then use the recursive search to return a Seq[JsValue] and then create a JsArray that can be validated as normal.

This works fine, it would be nice if we didn't have to define a separate Reads[T] though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM