Search code examples
jsonscalaplayframework-2.0playframework-json

How do I avoid nested Array when serializing a Map with Play Writes?


I am trying to serialize a map using the Json library from Play. I wrote my own Writes since there is none for Maps.

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

object NestedArray extends App {
  val m: Map[Int, String] = Map(1 -> "one", 2 -> "two")

  implicit val mWrites = new Writes[Map[Int, String]] {
    def writes(m: Map[Int, String]) = arr(
      m.keys.map(k => {
        obj("key" -> k.toString,
          "value" -> m(k)
        )
      })
    )
  }

  val j = toJson[Map[Int, String]](m)
  println(prettyPrint(j))
}

The output is this:

[ [ {
  "key" : "1",
  "value" : "one"
}, {
  "key" : "2",
  "value" : "two"
} ] ]

As you can see there are two pairs of [ ] around the items. When I use a Wrapper class around the map I only get one pair of [ ].

case class Wrap(m: Map[Int, String])
val w = new Wrap(m)

implicit val wrapWrites = new Writes[Wrap] {
  def writes(w: Wrap) = obj(
    "m" -> w.m.keys.map(k => {
      obj("key" -> k.toString,
        "value" -> w.m(k)
      )
    })
  )
}

val j2 = toJson[Wrap](w)
println(prettyPrint(j2))

Output:

{
  "m" : [ {
    "key" : "1",
    "value" : "one"
  }, {
    "key" : "2",
    "value" : "two"
  } ]
}

Is there a way to achieve that without a wrapper class?


Solution

  • Json.arr makes a JSON array from it's argument list. Since the first argument is itself a sequence, the result is a sequence of a sequence.

    E.g.

    scala> Json.arr(1,2,3)
    res1: play.api.libs.json.JsArray = [1,2,3]
    
    scala> Json.arr(List(1,2,3))
    res2: play.api.libs.json.JsArray = [[1,2,3]]
    

    Removing the call to arr and converting the Iterable directly to JSON using toJson removes the nested array

    import play.api.libs.json.Json._
    import play.api.libs.json._
    
    object NestedArray extends App {
      val m: Map[Int, String] = Map(1 -> "one", 2 -> "two")
    
      implicit val mWrites = new Writes[Map[Int, String]] {
        def writes(m: Map[Int, String]): JsValue = 
          Json.toJson(m.keys.map(k => {
            obj("key" -> k.toString,
              "value" -> m(k)
            )
          }))
      }
    
      val j = toJson[Map[Int, String]](m)
      println(prettyPrint(j))
    }