I'm implementing a Scala/Play API (apiA) which consumes an API (apiB) that I have no control over. ApiB returns a JSON response which in some cases has JSON embedded into strings. Example:
{
"name":"some_name",
"scores": "[[10,15]]",
"data": "{\"attr1\":\"value1\",\"attr2\":\"value3\"}"
}
ApiA needs to access the value of name
before passing all the data along to the client and it should be provided to the client as proper JSON.
I'm considering parsing the raw response into a
case class Response(name: String, scores: JsValue, data: JsValue)
or
case class Response(name: String, scores: Seq[Seq[Int]], data: Map[String, String])
For now it doesn't really matter what type scores
and data
are parsed into as long as it's not a string containing JSON.
Now, if the JSON response was well formed writing format/reads/writes would be straightforward but I'm sort of breaking my head on how to got about converting the content into JSON before parsing into the final types.
Any help would be appreciated.
case class Response(name: String, scores: Seq[Seq[Int]], data: Map[String, String])
import play.api.libs.json._
val stringified = Reads[JsValue] {
_.validate[String].flatMap { raw =>
try {
JsSuccess(Json.parse(raw))
} catch {
case cause =>
JsError(cause.getMessage)
}
}
}
implicit val respReads = Reads[Response] { js =>
for {
name <- (js \ "name").validate[String]
scores <- (js \ "scores").validate(
stringified).flatMap(_.validate[Seq[Seq[Int]]])
data <- (js \ "data").validate(
stringified).flatMap(_.validate[Map[String, String]])
} yield Response(name, scores, data)
}
Json.parse("""{
"name":"some_name",
"scores": "[[10,15]]",
"data": "{\"attr1\":\"value1\",\"attr2\":\"value3\"}"
}""").validate[Response]
// JsSuccess(Response(some_name,Vector(Vector(10, 15)),Map(attr1 -> value1, attr2 -> value3)),)