3

Trying to write a json format for an entity which contains a Map of Option. It throws following error

Error:(8, 68) No instance of play.api.libs.json.Format is available for scala.Predef.Map[java.lang.String, scala.Option[scala.Double]] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)

Code snippet:

import play.api.libs.json.{Json, OFormat}

val a: Map[String, Option[Double]] = Map("a" -> None)

case class Person(x: Map[String, Option[Double]])

object Person {
  implicit val personFormat: OFormat[Person] = Json.format[Person]
}

Json.toJson(Person(a))
Rich Dougherty
  • 3,174
  • 19
  • 23
Sujit Kamthe
  • 613
  • 9
  • 12

2 Answers2

4

You can define implicits:

  import scala.collection.Map

  object Person {
    implicit object MapReads extends Reads[Map[String, Option[Double]]] {
      def reads(jsValue: JsValue): JsResult[Map[String, Option[Double]]] = jsValue match {
        case JsObject(map) => JsSuccess(
          map.mapValues {
            case JsNumber(d) => Some(d.toDouble)
            case _ => None
          }
        )
        case _ => JsError()
      }
    }

    implicit object MapWrites extends Writes[Map[String, Option[Double]]] {
      def writes(map: Map[String, Option[Double]]): JsValue =
        JsObject(map.mapValues(optd => JsNumber(optd.getOrElse[Double](0.0))))
    }

    implicit val personFormat: OFormat[Person] = Json.format[Person]
  }

  println(Json.toJson(Person(a)))//{"x":{"a":0}}

Based on answer.

Dmytro Mitin
  • 34,874
  • 2
  • 15
  • 47
  • Mapping the None value to 0.0 may not be a good solution, as it's a valid value. None should be mapped to null. – Sujit Kamthe Nov 09 '17 at 04:24
  • 1
    Made following changes to implicit writes and now it returns null for None. `def writes(map: Map[String, Option[Double]]): JsValue = JsObject(map.mapValues { case None => JsNull case Some(x) => JsNumber(x) })` – Sujit Kamthe Nov 09 '17 at 04:29
  • @SujitKamthe Yes, this was just an example how to define such implicits. – Dmytro Mitin Nov 09 '17 at 08:44
0

The problem seems to be the inability of play-json macros to work with nested type variables:

Map[String, Option[Double]]

You could use an intermediate type wrapping Option

import play.api.libs.json.{Json, OFormat}

case class OptionDouble(value: Option[Double])
case class Person(x: Map[String, OptionDouble])

implicit val optionDoubleFormat = Json.format[OptionDouble]
implicit val personFormat = Json.format[Person]

val a: Map[String, OptionDouble] = Map("a" -> OptionDouble(None))
Json.toJson(Person(a))

Another option could be to write the formatter by hand.