Given the following case class:
case class ValueItem(key: String, value: String)
and the following json formatter:
implicit val valueItemFormat: Format[ValueItem] = (
(__ \ "key").format[String] and
(__ \ "value").format[String])(ValueItem.apply, unlift(ValueItem.unapply))
a json representation of a ValueItem instance like
ValueItem("fieldname", "fieldValue")
is
{ "key" : "fieldName" , "value" : "fieldValue" }
I'm wondering how to get a json in a flat key/value serialization like
{ "fieldName" : "fieldValue" }
I can't think of a nice way to do this using combinators, since most approaches there require a more direct way to map values at paths to case class fields.
Here's a solution that'll work for objects like {"fieldName" : "fieldValue"}
.
import play.api.libs.json._
import play.api.data.validation.ValidationError
implicit val fmt: Format[ValueItem] = new Format[ValueItem] {
def reads(js: JsValue): JsResult[ValueItem] = {
js.validate[JsObject].collect(ValidationError("Not a key-value pair")) {
case JsObject(Seq((key, str: JsString))) => ValueItem(key, str.value)
}
}
def writes(v: ValueItem): JsValue = Json.obj(v.key -> v.value)
}
I've resorted to defining reads
and writes
manually, as you can see. The Reads
is the tricky part, as we're not used to pulling path names into case classes. We can validate
the object as a JsObject
first, then collect
only objects that match the exact structure we're looking for (only one key-value pair, where the value is a JsString
). The Writes
is much more straightforward, as Json.obj
can do exactly what we want.
In action:
scala> Json.parse(""" { "fieldName" : "fieldValue" } """).validate[ValueItem]
res0: play.api.libs.json.JsResult[ValueItem] = JsSuccess(ValueItem(fieldName,fieldValue),)
scala> val item = ValueItem("myKey", "myValue")
item: ValueItem = ValueItem(myKey,myValue)
scala> Json.toJson(item)
res2: play.api.libs.json.JsValue = {"myKey":"myValue"}