2

Using Play 2.5 I cannot seem to serialize a Map[SomeCaseClass, String]

case class SomeCaseClass(value: String)

implicit val formatSomeCaseClass = Json.format[SomeCaseClass]

Json.toJson(Map[SomeCaseClass, String](SomeCaseClass("") -> ""))

Errors with

No Json serializer found for type scala.collection.immutable.Map[SomeCaseClass,String]. Try to implement an implicit Writes or Format for this type.

Unless i'm missing something obvious, there is an implicit format for that type immediately above.

If I try something more simple like:

Json.toJson(Something(""))
Json.toJson(Map[String, String]("" -> ""))

It works fine. What am I missing when using a Map with a more complex type e.g. SomeCaseClass?

pme
  • 14,156
  • 3
  • 52
  • 95
Eduardo
  • 6,900
  • 17
  • 77
  • 121

2 Answers2

4

I think the problem here comes from json. Maps get converted to JSON objects which consist of key/value pairs. The keys in those objects must be strings.

So a Map[String, T] can get converted to a json object but not an arbitrary Map[U, T].

Jack Bourne
  • 386
  • 5
  • 10
  • Yep I think you're right, I swapped the key and value around and it works, thanks – Eduardo Jun 05 '19 at 08:41
  • Given my case class is just a wrapper around a String, can you think of any way I can get it to work? – Eduardo Jun 05 '19 at 09:01
  • The simplest way may be to just remove your case class – Jack Bourne Jun 05 '19 at 09:02
  • The reason I ask is I had hoped to substitute my case class with an Enum, so it would just serialize to a String – Eduardo Jun 05 '19 at 10:24
  • If you only want to write it, you could map your Enum values before writing to the corresponding strings: `mapToWrite.map { case (key, value) => (key.toString, value) }` – Jack Bourne Jun 05 '19 at 10:40
1

@Jack Bourne is correct. A map is treated as a JsObject in play json and because of that the key must be serialisable to a string value.

Here is a sample code that you can use to define a map format

import play.api.libs.json._

case class SomeCaseClass(value: String)

implicit val formatSomeCaseClass = Json.format[SomeCaseClass]

Json.toJson(Map[SomeCaseClass, String](SomeCaseClass("") -> ""))

implicit val someCaseClassToStringFormat = new Format[Map[SomeCaseClass, String]] {
override def writes(o: Map[SomeCaseClass, String]): JsValue = {
  val tuples = o.toSeq.map {
    case (key, value) => key.value -> Json.toJsFieldJsValueWrapper(value)
  }
  Json.obj(tuples: _*)
}

override def reads(json: JsValue): JsResult[Map[SomeCaseClass, String]] = {
  val resultMap: Map[SomeCaseClass, String] = json.as[JsObject].value.toSeq.map {
    case (key, value) => Json.fromJson[SomeCaseClass](value) match {
      case JsSuccess(someCaseClass, _) => someCaseClass -> key
      case JsError(errors) => throw new Exception(s"Unable to parse json :: $value as SomeCaseClass because of ${JsError.apply(errors)}")
    }
  }(collection.breakOut)
  JsSuccess(resultMap)
 }
}
Keshaw Kumar
  • 317
  • 1
  • 4
  • 15