2

I am using built-in jerson with playframework 2, and all I want is to serialize map, containing values of different type:

object AppWriter extends Writes[Application] {
    def writes(app: Application): JsValue = {
        Json.toJson(Map(
            "id" -> app.getId.toString,
            "name" -> app.getName,
            "users" -> Seq(1, 2, 3)
        ))
    }
}

In this case I have:

No Json deserializer found for type scala.collection.immutable.Map[java.lang.String,java.lang.Object]. 
Try to implement an implicit Writes or Format for this type

Navigatin through framework code shows that there is serializer for Map[String,V] types implicit def mapWrites[V] .. , but I cannot understand why it doesn't applied.

Can anybody help me?

UPD: I found simple workaround:

object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = {
    Json.toJson(Map[String, JsValue](
        "id" -> JsNumber(BigDecimal(app.getId)),
        "name" -> JsString(app.getName),
        "users" -> JsArray(Seq(1, 2, 3).map(x => JsNumber(x)))
    ))
}

}

but this is not so elegant...

Alex Povar
  • 4,890
  • 3
  • 29
  • 44
  • Why are you storing data in a map like that? A map is generally a key value store where all the values are of the same type. You should just use a case class to model that data. – Dominic Bou-Samra Dec 16 '12 at 13:02
  • @DominicBou-Samra, idea sounds good, but looks like there is no method for generating JsValue from case class, string only. – Alex Povar Dec 16 '12 at 13:33

1 Answers1

1

The standard way to do this is by creating a JsObject from the individual key-value pairs for the fields—not by putting the pairs into a map. For example, assuming your Application looks like this:

case class Application(getId: Int, getName: String)

You could write:

import play.api.libs.json._, Json.toJson

implicit object AppWriter extends Writes[Application] {
  def writes(app: Application): JsValue = JsObject(
    Seq(
      "id"    -> JsNumber(app.getId),
      "name"  -> JsString(app.getName),
      "users" -> toJson(Seq(1, 2, 3))
    )
  )
}

And then:

scala> toJson(Application(1, "test"))
res1: play.api.libs.json.JsValue = {"id":1,"name":"test","users":[1,2,3]}

Note that you don't need to spell out how to serialize the Seq[Int]—the default Format instances will do the work for you.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 1
    Thanks a lot. But is there any place where such workflow is documented? – Alex Povar Dec 16 '12 at 16:40
  • The [high-level documentation](http://www.playframework.org/documentation/2.0/ScalaJson) is currently very weak on how to write your own `Format`, etc. instances. Your best bet is probably the [API docs](http://www.playframework.org/documentation/api/2.0/scala/play/api/libs/json/package.html) and third-party tutorials (and Stack Overflow, of course). – Travis Brown Dec 16 '12 at 16:51
  • I've made attempt to dig into sources, but my one-week scala knowledge too poor for understand whole mechanism, so play's documentation and even jerkson's is really what each newbie need. – Alex Povar Dec 16 '12 at 16:59
  • I'd suggest reading about [type classes](http://en.wikipedia.org/wiki/Type_class)—it's a very useful construct that can be difficult to get your head around at first. It's essentially evidence that you can perform some set of operations on an instance of some type. So having an implicit `Writes[Application]` instance means that you can serialize any `Application` as JSON. – Travis Brown Dec 16 '12 at 17:05