Search code examples
scalagenericsspray-json

JsonFormat for abstract class with generic parameter


I am trying to write a JsonFormat for an abstract class with a generic parameter that looks like something like this:

abstract class Animal[A] {
    def data: A
    def otherStuff: String = "stuff"
}
case class CatData(catField: String)
case class Cat(data: CatData) extends Animal[CatData]

So far my attempt at this looks like:

object AnimalProtocol extends DefaultJsonProtocol {

    implicit val catDataFormat = jsonFormat1(CatData)
    implicit val catFormat = jsonFormat1(Cat)

    implicit def animalFormat[T <: Animal[T]](t: T)(implicit fmt: JsonWriter[T]) = new RootJsonFormat[Animal[T]] {
    def write(obj: Animal[T]) = obj match {
        case x: Cat => catFormat.write(x)
    }

    def read(json: JsValue) = ???
  }

Now, if I try to do this:

import AnimalProtocol._
val cat: Animal[CatData] = Cat(CatData("this is cat data"))

I get the compiler error:

Cannot find JsonWriter or JsonFormat type class for Animal[CatData]

How can I make it work? In the end I want to write json with the fields in Animal and with data set to whatever case class applies.


Solution

  • You need to provide a type parameter for both the generic field and the subclass of Animal in your implicit def:

    object AnimalProtocol2 extends DefaultJsonProtocol {
    
      implicit val catDataFormat = jsonFormat1(CatData)
    
      implicit def animalFormat[A, T <: Animal[A]](implicit fmt: JsonWriter[A]): RootJsonFormat[T] = new RootJsonFormat[T] {
        def write(obj: T) = {
          JsObject(
            "data" -> obj.data.toJson,
            "otherStuff" -> obj.otherStuff.toJson
          )
        }
    
        def read(json: JsValue) = ???
      }
    }
    

    That also allows you to get rid of pattern matching on subclasses inside animalFormat.