Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] support jsonDiscriminator in case class #1056

Open
alphaho opened this issue Jan 5, 2024 · 4 comments · May be fixed by #1112 or #1113
Open

[Feature Request] support jsonDiscriminator in case class #1056

alphaho opened this issue Jan 5, 2024 · 4 comments · May be fixed by #1112 or #1113

Comments

@alphaho
Copy link

alphaho commented Jan 5, 2024

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:

  1. 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
  2. we can support reading the parent sealed trait's @jsonDiscriminator when we deriving case class's codec
@jdegoes
Copy link
Member

jdegoes commented Apr 25, 2024

/bounty $100

Copy link

algora-pbc bot commented Apr 25, 2024

💎 $100 bounty • ZIO

Steps to solve:

  1. Start working: Comment /attempt #1056 with your implementation plan
  2. Submit work: Create a pull request including /claim #1056 in the PR body to claim the bounty
  3. 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 bountyShare on socials

Attempt Started (GMT+0) Solution
🟢 @987Nabil #1112
🟢 @pablf #1113

Copy link

algora-pbc bot commented May 11, 2024

💡 @987Nabil submitted a pull request that claims the bounty. You can visit your bounty board to reward.

@pablf pablf linked a pull request May 12, 2024 that will close this issue
Copy link

algora-pbc bot commented May 12, 2024

💡 @pablf submitted a pull request that claims the bounty. You can visit your bounty board to reward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants