First Attempt:
So far I have tried spray-json. I have:
trait Base
case class A ( id: String) extends Base
case class B (id: String) extends Base
Now, for serializing and deserializing my Base type, I have the code:
implicit object BaseFormat extends RootJsonFormat[Base]{
def write(obj: Base): JsValue = {
obj match {
case a: A => a.toJson
case b: B => b.toJson
case unknown @ _ => serializationError(s"Marshalling issue with ${unknown}")
}
}
def read(json: JsValue): Base = {
//how to know whether json is encoding an A or a B?
}
}
The problem is that, for implementing the read method for deserialization, I can't figure out a way to know whether the JsValue is encoding an A or a B.
Second Attempt:
For solving this in spray-json, I ended up simply renaming the field id in A to aID, and in B to bID.
Third Attempt:
Since spray-json was not as sophisticated as the alternative libraries such as zio-json or circe, which handle this issue by themselves without additional code, I started using zio-json
Now I get the error
magnolia: could not infer DeriveJsonEncoder.Typeclass for type
for all the case classes taking type parameters. Also, it has problems with chained trait inheritance. It seems like circe uses magnolia, too. So it’s likely this would be replicated with circe, as well.
Any help would be appreciated.
You should address this problem using a json encoding/decoding library. Here is an example using circe and it's semi-automatic mode.
Since you mentionned in the comments that you are struggling with generic types in your case classes, I'm also including that. Basically, to derive an encoder or decoder for a class
Foo[T]that contains aT, you have to prove that there is a way to encode and decodeT. This is done by asking for an implicitEncoder[T]andDecoder[T]where you deriveEncoder[Foo[T]]andDecoder[Foo[T]]. You can generalize this reasoning to work with more than one generic type of course, you just have to have an encoder/decoder pair implicitly available where you derive the encoder/decoder for the corresponding case class.Try it live on Scastie
Note that a different encoder/decoder for
Foo[T]needs to be generated for each typeTthat you are using, which is why the encoder and decoder derivation forFoohave to be methods, not values like forBar.There is also a fully-automatic mode, but it tends to produce compile-time errors that are harder to debug for beginner, so I would start with semi-auto. Another problem, is that auto-mode can take much longer to compile on large projects. If you're feeling adventurous, it's even more beautiful when you don't make mistakes!
Try it live on Scastie