0
votes

I have following classes

import spray.json._
sealed trait Base
case class Foo[K, V](key : K, value : V) extends Base
case class Bar[K, V](key : V, value : K) extends Base 

their corresponding json convertors

implicit def baseJsonConvertor[K: JsonFormat, V: JsonFormat] = new JsonFormat[Base] {
  override def read(json: JsValue): Base =
    throw new SerializationException("Don't use this for reading...")

  override def write(obj: Base): JsValue = obj match {
    case e : Foo[K, V] => jsonFormat2(Foo.apply[K, V]).toJson
    case e : Bar[K, V] => jsonFormat2(Bar.apply[V, K]).toJson
  }
}
implicit def fooJsonConvertor[K: JsonFormat, V: JsonFormat] = jsonFormat2(Foo.apply[K, V])
implicit def barJsonConvertor[K: JsonFormat, V: JsonFormat] = jsonFormat2(Bar.apply[V, K])

When I try this

val list = List(Foo[String, Int]("One", 1), Bar(2, "Two") ).map(_.toJson)

I get this error as

:216: warning: abstract type K in type pattern Foo[K,V] is unchecked since it is eliminated by erasure case e : Foo[K, V] => jsonFormat2(Foo.apply[K, V]).toJson

Cannot find JsonWriter or JsonFormat type class for Product with Serializable with Base val list = List(FooString, Int ).map(_.toJson)

I can't use context bound in match-case in write method of baseJsonConvertor.

Can someone help with the work around for this problem?

2

2 Answers

0
votes

This example will work in this particular case:

import org.scalatest.{Matchers, WordSpecLike}



object O {

  import spray.json._
  import spray.json.DefaultJsonProtocol._

  sealed trait Base[K, V]

  case class Foo[K, V](key: K, value: V) extends Base[K, V]

  case class Bar[K, V](key: V, value: K) extends Base[K, V]

  implicit def fooJsonConvertor[K: JsonFormat, V: JsonFormat] = jsonFormat2(Foo.apply[K, V])

  implicit def barJsonConvertor[K: JsonFormat, V: JsonFormat] = jsonFormat2(Bar.apply[V, K])



  implicit def baseJsonConvertor[K: JsonFormat: Manifest, V: JsonFormat: Manifest] = new JsonFormat[Base[K, V]] {
    override def read(json: JsValue): Base[K, V] =
      throw new SerializationException("Don't use this for reading...")

    override def write(obj: Base[K, V]): JsValue = obj match {
      case e@Foo(_: K, _: V) => implicitly[JsonFormat[Foo[K,V]]].write(e.asInstanceOf[Foo[K, V]])

      case e@Bar(_: V, _: K) => implicitly[JsonFormat[Bar[K,V]]].write(e.asInstanceOf[Bar[K, V]])
    }
  }




}

class Spec extends WordSpecLike with Matchers {

  import O._
  import spray.json._
  import spray.json.DefaultJsonProtocol._
  "test" should {

    "test" in {

      val list = List[Base[String, Int]](Foo[String, Int]("One", 1), Bar(2, "Two")).map(_.toJson)

      println(list)


    }



  }


}

However you can not use Base without type parameters because you won't be able to derive JsonFormat without them. Please note that you must declare illicit jsonFormats in reverse order they used. I mean you must first declare implicit for JsonFormat Foo and Bar and then for Base. Otherwise they won't be able to be discovered. You also should use implicit Manifest to be able to match safely.

If you actually got more complicated case with variable number of type parameters in child classes this won't work for you. I don't think the case with variable number of arguments will be resolvable efficiently without eliminating type parameters in child classes. Also note that object O containing implicits must be declared before Spec if it is in the same file with Spec to be able to be visible in Spec.

0
votes

In general, you can't create JsonFormat for a trait with generic children classes (especially if they have different sets of generic parameters).

For example, this trait can't be serialized with spray-json:

sealed trait Base
case class Foo[K, V](key: K, value: V) extends Base
case class Bar[K, V, T](key: V, value: K, param: T) extends Base 

I think you should wrap your generic classes into element, who knows all generic parameters.

Like Either in this example:

import spray.json.DefaultJsonProtocol._
import spray.json._
case class Foo[K, V](key: K, value: V)
case class Bar[K, V](key: V, value: K)
implicit def fooJsonFormat[K: JsonFormat, V: JsonFormat] = jsonFormat2(Foo.apply[K, V])
implicit def barJsonFormat[K: JsonFormat, V: JsonFormat] = jsonFormat2(Bar.apply[V, K])
val list = List[Either[Foo[String, Int], Bar[Int, String]]](Left(Foo("One", 1)), Right(Bar(2, "Two")))
println(list.toJson)