Search code examples
scalaplayframeworkplay-json

Scala key/value case class to Json


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" }

Solution

  • 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"}