supernatural
supernatural

Reputation: 1197

How to define implicits for Map of two classes?

case class Apple(id:String, name:String)
case class Fruit(id:String,ftype:String)

case class Basket(b:Map[Fruit,Apple])

How to define the play implicits as the below definition are not enough.

 implicit val format: Format[Fruit] = Json.format
 implicit val format: Format[Apple] = Json.format

This isn't working :

 implicit val format: Format[Basket] = Json.format

Upvotes: 0

Views: 88

Answers (2)

pme
pme

Reputation: 14803

The formatter are ok, but they only work for Case Classes.

So all you have to do is to adjust them:

case class Apple(id:String, name:String)
case class Fruit(id:String,ftype:String)

case class Basket(b:Map[Fruit,Apple])

Update

Ok there is another problem. JSON has a restriction that the Key of a Map must be a String.

See my answer here: https://stackoverflow.com/a/53896463/2750966

Ok here an example for Play < 2.8:

  implicit val formata: Format[Apple] = Json.format

  implicit val mapReads: Reads[Map[Fruit, Apple]] = (jv: JsValue) =>
    JsSuccess(jv.as[Map[String, Apple]].map { case (k, v) =>
      (k.split("::").toList match {
        case id :: ftype :: _ => Fruit(id, ftype)
        case other => throw new IllegalArgumentException(s"Unexpected Fruit Key $other")
      }) -> v
    })

  implicit val mapWrites: Writes[Map[Fruit, Apple]] = (map: Map[Fruit, Apple]) =>
    Json.toJson(map.map { case (fruit, o) =>
      s"${fruit.id}::${fruit.ftype}" -> o
    })
  implicit val jsonMapFormat: Format[Map[Fruit, Apple]] = Format(mapReads, mapWrites)

  implicit val formatb: Format[Basket] = Json.format

With this example Data it works:

  val basket = Basket(Map(Fruit("12A", "granate") -> Apple("A11", "The Super Apple"),
     Fruit("22A", "gala") -> Apple("A21", "The Gala Premium Apple")))
  val json = Json.toJson(basket) // >> {"b":{"12A::granate":{"id":"A11","name":"The Super Apple"},"22A::gala":{"id":"A21","name":"The Gala Premium Apple"}}}
  json.as[Basket] // >> Basket(Map(Fruit(12A,granate) -> Apple(A11,The Super Apple), Fruit(22A,gala) -> Apple(A21,The Gala Premium Apple)))

Here the Scalafiddle

Upvotes: 2

cbley
cbley

Reputation: 4608

Here is a possible implementation using Play JSON 2.8:

import play.api.libs.json._

case class Apple(id:String, name:String)
case class Fruit(id:String,ftype:String)

case class Basket(b:Map[Fruit,Apple])

object Apple {
  implicit val format = Json.format[Apple]
}

object Basket {
  implicit val keyReads: KeyReads[Fruit] = s => ???
  implicit val keyWrites: KeyWrites[Fruit] = f => s"${f.id}/${f.ftype}"

  implicit val format = Json.format[Basket]
}

val b = Basket(Map(Fruit("1", "sw") -> Apple("2", "boskop")))

Json.stringify(Json.toJson(b)) // -> {"b":{"1/sw":{"id":"2","name":"boskop"}}}

https://scastie.scala-lang.org/XbFY6amKSb6BCVCrGmgrBQ

Upvotes: 1

Related Questions