Mikesname
Mikesname

Reputation: 8901

Play 2.1 Scala - JSON - Symmetric Format object with nested data structure not serializing correctly

I'm having some trouble writing a Format object for some JSON in Play 2.1 Scala. My (greatly simplified) JSON looks like this:

{
  "id": "id-1",
  "data": {
    "foo": "teststr1",
    "bar": "teststr2"
  }
}

I want to serialize/deserialize this to a slightly simpler, flattened format:

case class TestData(id: String, foo: String, bar: String)

My initial attempt at this looks like so:

implicit val testCaseFormat: Format[TestData] = (
  (__ \ "id").format[String] and
  (__ \ "data" \ "foo").format[String] and
  (__ \ "data" \ "bar").format[String]
)(TestData.apply, unlift(TestData.unapply))

That works fine when reading the data, but when writing only the last "data" value (bar) exists in the output JSON, presumably because the data object is being overwritten for each subsequent value:

{
   "id" : "id-1",
   "data":{
     "bar":"teststr2"
   }
 }

Is there a correct way to do this flattening and unflattening, without resorting to separate Reads and Writes objects? My actual data structures are quite large (although not too complex) so conciseness is good.

Upvotes: 0

Views: 495

Answers (1)

EECOLOR
EECOLOR

Reputation: 11244

If I understand correctly you could/should create your format in a regular fashion:

(__ \ "id").format[String] and
(__ \ "data").format(
  (__ \ "foo").format[String] and
  (__ \ "bar").format[String]
  tupled
)

And then just supply different apply and unapply methods.

def specialApply(data:(String, (String, String))) = {
  val(id, (foo, bar)) = data
  TestData(id, foo, bar)
}

def specialUnapply(data:TestData):Option[(String, (String, String))] =
  Option(data) map { testData =>
    (testData.id, (testData.foo, testData.bar))
  }

Edit

You can avoid adding the methods to the companion objects by composing the apply and unapply methods in place:

implicit val testCaseFormat: Format[TestData] = (
    // ...
)(TestData.tupled.compose { data:(String, (String, String)) => 
    val (id, (foo, bar)) = data
    (id, foo, bar)
  }), unlift(TestData.unapply))

Upvotes: 2

Related Questions