0
votes

I'm using the Spray JSON library to serialize our case classes into JSON. The problem is that we have some mutually recursive definitions. I am working from the example for serializing sealed traits here: http://www.cakesolutions.net/teamblogs/2012/11/30/spray-json-and-adts/

Here is a simple example that works. Please note the definition of C:

import spray.json._
import DefaultJsonProtocol._

sealed trait BaseTrait
sealed trait SpecializedTrait extends BaseTrait

case class A(argA: BaseTrait, foo: Int) extends SpecializedTrait
case class B(argB: BaseTrait, foo: Int) extends SpecializedTrait

case class C(foo: Int) extends BaseTrait

object BaseTrait {
  implicit val cJsonFormat = jsonFormat1(C)

  implicit object BaseTraitJsonFormat extends RootJsonFormat[BaseTrait] {
    override def write(obj: BaseTrait): JsValue = throw new IllegalStateException("Not Implemented")
    override def read(v: JsValue): BaseTrait = throw new IllegalStateException("Not Implemented")
  }
}

object SpecializedTrait {
  implicit val aJsonFormat = jsonFormat2(A)
  implicit val bJsonFormat = jsonFormat2(B)

  implicit object SpecializedTraitJsonFormat extends RootJsonFormat[SpecializedTrait] {
    override def write(obj: SpecializedTrait): JsValue = throw new IllegalStateException("Not Implemented")
    override def read(v: JsValue): SpecializedTrait = throw new IllegalStateException("Not Implemented")
  }
}

When you change the definition of "C" to the original mutually recursive definition, .

...
case class C(argC: SpecializedTrait, foo: Int) extends BaseTrait

object BaseTrait {
  implicit val cJsonFormat = jsonFormat2(C)

...

Now obviously, you can't just say no, I can't have mutually recursive data structures. It seems the compiler is stumbling because of resolution rules for implicit objects.

Is there a workaround for this? I got around it by changing the objects to anonymous class declarations, i.e.

import spray.json._
import DefaultJsonProtocol._

sealed trait BaseTrait
sealed trait SpecializedTrait extends BaseTrait

case class A(argA: BaseTrait, foo: Int) extends SpecializedTrait
case class B(argB: BaseTrait, foo: Int) extends SpecializedTrait

case class C(argC: SpecializedTrait, foo: Int) extends BaseTrait

object BaseTrait {
  implicit val cJsonFormat : RootJsonFormat[C] = jsonFormat2(C)

  implicit val baseTraitFormat : RootJsonFormat[BaseTrait] = new RootJsonFormat[BaseTrait] {
    def write(obj: BaseTrait): JsValue = throw new IllegalStateException("Not Implemented")
    def read(v: JsValue): BaseTrait = throw new IllegalStateException("Not Implemented")
  }
}

object SpecializedTrait {
  implicit val aJsonFormat : RootJsonFormat[A] = jsonFormat2(A)
  implicit val bJsonFormat : RootJsonFormat[B] = jsonFormat2(B)

  implicit val specializedTraitFormat : RootJsonFormat[SpecializedTrait] = new RootJsonFormat[SpecializedTrait] {
    override def write(obj: SpecializedTrait): JsValue = throw new IllegalStateException("Not Implemented")
    override def read(v: JsValue): SpecializedTrait = throw new IllegalStateException("Not Implemented")
  }
}

That last snippet works, note the change from "implicit object" to "implicit val" and the subsequent anonymous class.

1

1 Answers

0
votes

Define the implicits in order and put them into one object.

import spray.json._
import DefaultJsonProtocol._

sealed trait BaseTrait
sealed trait SpecializedTrait extends BaseTrait

case class A(argA: BaseTrait, foo: Int) extends SpecializedTrait
case class B(argB: BaseTrait, foo: Int) extends SpecializedTrait
case class C(argC: SpecializedTrait, foo: Int) extends BaseTrait

object SomeProtocol {
  implicit object BaseTraitJsonFormat extends RootJsonFormat[BaseTrait] {
    override def write(obj: BaseTrait): JsValue = throw new IllegalStateException("Not Implemented")
    override def read(v: JsValue): BaseTrait = throw new IllegalStateException("Not Implemented")
  }
  implicit object SpecializedTraitJsonFormat extends RootJsonFormat[SpecializedTrait] {
    override def write(obj: SpecializedTrait): JsValue = throw new IllegalStateException("Not Implemented")
    override def read(v: JsValue): SpecializedTrait = throw new IllegalStateException("Not Implemented")
  }
  implicit val aJsonFormat = jsonFormat2(A)
  implicit val bJsonFormat = jsonFormat2(B)
  implicit val cJsonFormat = jsonFormat2(C)
}