Search code examples
jsonscalaplayframeworkcombinators

Defining Writes for case class that contains property whose type is a Map . Json / Scala / PlayFramework


I have a case class defined this way:

scala> import play.api.libs.json._
  import play.api.libs.json._
scala> import play.api.libs.functional.syntax._
  import play.api.libs.functional.syntax._

scala> type MapIntString = Map[Int, String]
  defined type alias MapIntString

case class Duh(a: MapIntString)
  defined class Duh

When I tried declaring its Writes this way, I got an error:

scala> implicit val duhWrites = Json.writes[Duh]
  <console>:25: error: No implicit Writes for MapIntString available.
  implicit val duhWrites = Json.writes[Duh]

So I resort to this..., still got an error:

scala> implicit val duhWrites: Writes[Duh] = (
     |   (JsPath \ "a").write[MapIntString]
     | )(unlift(Duh.unapply _))
  <console>:26: error: overloaded method value write with alternatives:
  (t: MapIntString)(implicit w: play.api.libs.json.Writes[MapIntString])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
  (implicit w: play.api.libs.json.Writes[MapIntString])play.api.libs.json.OWrites[MapIntString]
  cannot be applied to (Duh => MapIntString)
  (JsPath \ "a").write[MapIntString]

Can somebody help me pointing out where I did wrong?

I feel that my understanding of this reads/writes combinators a bit shaky. I posted related question here (when I was trying to understand what was going on): What is this "and" in ScalaJsonCombinator (when defining a Writes)?

If you know the best way to implement the Writes and Reads for "Duh" please share.

Thanks in advance!


Solution

  • There are no predefined Writes for Map[Int, String], only Map[String, A]. This is because all JSON keys must be a String, to Int keys don't really make any sense. You can fix this by defining one that converts the Int keys to Strings.

    type MapIntString = Map[Int, String]
    
    implicit val mapIntWrites = new Writes[MapIntString] {
        def writes(m: MapIntString): JsValue = 
            Json.toJson(m.map { case (k, v) => (k.toString, v)} )
    }
    
    scala> implicit val duhWrites = Json.writes[Duh]
    duhWrites: play.api.libs.json.OWrites[Duh] = play.api.libs.json.OWrites$$anon$2@50973f47