2

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" }
Daenyth
  • 35,856
  • 13
  • 85
  • 124
Dario
  • 255
  • 1
  • 10
  • I suspect you might have to define separate Reads and Writes instead of using Format for this one. – Daenyth May 09 '15 at 11:32
  • Yes, that's a good point. I've already been there but I've faced the problem of finding the proper jspath with no parameter name, but knowing the position. Something like "key" comes before "value" – Dario May 09 '15 at 16:39

2 Answers2

1

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"}
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
0

Play released it's module for dealing with JSON independent from Play Framework, Play WS

Made a blog post about reading JSON to case classes, but write is pretty similar. check it out at http://pedrorijo.com/blog/scala-json/

Using case classes, and Play WS (already included in Play Framework) you case convert between json and case classes with a simple one-liner implicit

case class User(username: String, friends: Int, enemies: Int, isAlive: Boolean)

object User {
  implicit val userJsonFormat = Json.format[User]
}
pedrorijo91
  • 7,635
  • 9
  • 44
  • 82