zio-json
zio-json copied to clipboard
[Feature Request] support jsonDiscriminator in case class
With a sealed trait like below, the anmailCodec
would emita type
property in the encoded json string as expected like: {"type": "dog", "name": "Snoopy"}
.
import zio.json._
@jsonDiscriminator("type")
sealed trait Animal
object Animal {
@jsonHint("dog")
case class Dog(name: String) extends Animal
object Dog {
implicit val dogCodec: JsonCodec[Dog] = DeriveJsonCodec.gen
}
@jsonHint("cat")
case class Cat(name: String, weight: Double) extends Animal
implicit val animalCodec: JsonCodec[Animal] = DeriveJsonCodec.gen
}
But sometimes in the client code, I want to use the more specific Dog
type instead of Animal
. That requires me to define the dogCodec
as above in Dog
's companion object.
However, the dogCodec
would not emit a type
property in the encoded json, and get us something like {"name": "Snoopy"}
instead. And it would not honor the type
property during decoding, accepting invalid input with unexpected type
.
For example, below test would fail as it parses the json successfully:
import zio.json._
import zio.test._
object AnimalCodecSpec extends ZIOSpecDefault {
override def spec: Spec[TestEnvironment, Any] =
test("this would fail, as it ignores the discriminator") {
val json = """{"type":"cat","name":"Kitty", "weight": 8}"""
val decoded = json.fromJson[Dog]
assertTrue(decoded.is(_.left.anything)) // decoded == Right(Dog(Kitty))
}
}
One workaround I have is to define extra sealed traits for the case classes like the Dog
type.
For example, to emit type
property for Dog
, we can:
@jsonDiscriminator("type")
sealed trait Animal2
object Animal2 {
@jsonDiscriminator("type")
sealed trait Dog extends Animal2 { // extra sealed trait needed as a workaround
val name: String
}
@jsonHint("dog")
case class DogImpl(name: String) extends Dog
object Dog {
implicit val dogCodec: JsonCodec[Dog] = DeriveJsonCodec.gen
}
@jsonHint("cat")
case class Cat(name: String, weight: Double) extends Animal2
implicit val animalCodec: JsonCodec[Animal2] = DeriveJsonCodec.gen
}
object Animal2CodecSpec extends ZIOSpecDefault {
override def spec: Spec[TestEnvironment, Any] =
test("this would success, as it honors the discriminator") {
val json = """{"type":"cat","name":"Kitty", "weight": 8}"""
val decoded = json.fromJson[Animal2.Dog]
assertTrue(decoded.is(_.left.anything)) // decoded == Left((invalid disambiguator))
}
}
However, this workaround is quite verbose, especially if we need multiple case class codecs to emit the discriminator. It would be better if:
- we can support
@jsonDiscriminator
in case class (with the risk that the case class's jsonDiscriminator value different from the parent's jsonDiscriminator value), or - we can support reading the parent sealed trait's
@jsonDiscriminator
when we deriving case class's codec
/bounty $100
💎 $100 bounty • ZIO
Steps to solve:
-
Start working: Comment
/attempt #1056
with your implementation plan -
Submit work: Create a pull request including
/claim #1056
in the PR body to claim the bounty - Receive payment: 100% of the bounty is received 2-5 days post-reward. Make sure you are eligible for payouts
Thank you for contributing to zio/zio-json!
Add a bounty • Share on socials
Attempt | Started (GMT+0) | Solution |
---|---|---|
🟢 @987Nabil | #1112 | |
🟢 @pablf | #1113 |
💡 @987Nabil submitted a pull request that claims the bounty. You can visit your bounty board to reward.
💡 @pablf submitted a pull request that claims the bounty. You can visit your bounty board to reward.