0

In short I'm dealing with JSON that has to look like:

{
  "data": JSON.stringify(...), // eg. JSON.stringify(null) or JSON.stringify({p: "v"})
  ...
}

I wish to parse it into a case class resembling something like:

case class Foo(data: JsObject, ...)

And the Reads[Food] I've defined looks like this:

val fooReads: Reads[Foo] = (
  (__ \"data").readNullable[String].map(_.filter(s => s != null && !s.isEmpty).map(Json.parse(_).as[JsObject]).getOrElse(JsObject(Seq()))),
  ...
)

But I keep getting the following error:

ValidationError(error.expected.jsobject,WrappedArray())

I'm pretty sure my Reads[Foo] is now resembling an ugly tumor that doesn't need to exist, so some help would be nice.

Nadir Muzaffar
  • 4,392
  • 1
  • 28
  • 44

2 Answers2

0

This? I did use corrected version of JSON above.You had readNullable above but case class data was not an option so I removed it.

 implicit val reads = (__ \ "data").read[JsObject].map { data => Foo(data)}

More of an FYI but Play JSON Combinators don't work with case classes with one field: How to turn json to case class when case class has only one field

Nullable version:

 case class Foo(data: Option[JsObject])
 implicit val reads:Reads[Foo] = (__ \ "data").readNullable[JsObject].map { data => Foo(data)}

Updated version to read serialized version of JSON string:

implicit val reads:Reads[Foo] = (__ \ "data").readNullable[String].map{
 case Some(x) => Foo(data = Json.parse(x).asOpt[JsObject])
 case _ => Foo(data=None)
}

I kept readNullable for the String read so library would handle checking if String was empty or null.

Community
  • 1
  • 1
Barry
  • 1,660
  • 1
  • 21
  • 44
  • Ah, well I tried to simplify by having only one property. Regardless, when trying to parse the data property straight into a JsObject I get a similar error. – Nadir Muzaffar Dec 11 '14 at 21:14
  • I might just end up going with the Option[JsObject] property instead because this is becoming far more trouble than it's worth. Although I try to avoid having an Optional value by using getOrElse for default values. – Nadir Muzaffar Dec 11 '14 at 21:15
  • 1
    Comes down to preference but I would prefer to work with Option type because its clear if you have Foo(None) nothing was there. If you choose empty seq or some other thing to represent empty (if this code is for more than you) they need to understand whatever you chose for empty means there was nothing in data etc. – Barry Dec 11 '14 at 21:18
  • Since the data property is a serialized JSON value, it seems Reads[Foo] needs to be defined, if using Option[JsObject], as: (__ \ "data").read[String].map(Json.parse(_).asOpt[JsObject]).map { data => Foo(data)}. Although I haven't tried the case of a missing property. – Nadir Muzaffar Dec 11 '14 at 21:44
  • Would you mind updating your answer to deal with the fact the data property is a serialized JSON value, and I can go ahead and accept your answer. – Nadir Muzaffar Dec 11 '14 at 22:09
  • Did you say data is JsValue already before reads? Not sure I follow all the comments. – Barry Dec 12 '14 at 00:06
  • The value of the data property is a serialized JSON object. So it first needs to be read[String] then Json.parse(_) before you can read[JsObject]. – Nadir Muzaffar Dec 12 '14 at 01:54
0

The solution I went for, given the following JSON (with more than one property):

{
  "data": "null", // JSON.stringify({p: "v"}) or JSON.stringify(null)
  "fullName": "JohnDoe"
}

Scala code:

implicit val fooReads: Reads[Foo] = (
  (__ \ "data").read[String].map(Json.parse(_).asOpt[JsObject]) and
  (__ \ "fullName").read[String]
)

case class Foo(data: Option[JsObject], fullName: String)

Still not handling the case of a missing data property and I'm not sure if there are any disadvantages to using asOpt[JsObject] so will likely need to refine this going forwards.

Nadir Muzaffar
  • 4,392
  • 1
  • 28
  • 44