Skip to content

Support a flag to enable always emitting the discriminator field #1148

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

Merged

Conversation

jamie-heller
Copy link
Contributor

Summary

This PR adds the alwaysEmitDiscriminator flag to CodecMakerConfig in order to support always emitting the discriminator field for ADT leaf classes. This new flag has no effect on generated decoders, and it does not effect encoder generation for classes that are not members of ADTs. This new flag also should have no effect on codecs derived for the root of the ADT.

Motivation

The motivation for this PR is the use case of transitioning an existing API from a different json library to jsoniter-scala, wherein previously the type discriminators of ADT leaf classes were always serialized by the prior json library and the clients have come to depend upon it.

As a simplified example, consider the following exemplary data model:

// The ADT
sealed trait Base
case class Foo(x: Int, y: String) extends Base
case class Bar(a: String, b: Double) extends Base

// Top level data models exposed by API
case class Alpha(foo: Foo)
case class Beta(bar: Bar)
case class Gamma(base: Base)

The default behavior of jsoniter derived codecs is to only serialize the type discriminators when they are strictly required -- that is if during derivation a value is statically known to be a concrete ADT Leaf type, the derived codecs neither serialize the type discriminator nor require it. Thus by default, the following json is generated for instances of Alpha, Beta, and Gamma respectively

Alpha(foo = Foo(x = 1, y = "a string"))
{
  "foo": {
    "x": 1,
    "y": "a string"
  }
}
Beta(bar = Bar(a = "a string", b = 1.2))
{
  "bar": {
     "a": "a string",
     "b": 1.2
   }
}

Note in this case the discriminator must be, and is, serialized

Gamma(base = Foo(x = 1, y = "a string"))
{
  "base": {
    "type": "Foo",
    "x": 1,
    "y": "a string"
  }
}

This PR enables setting a new flag in the CodecMakerConfig, namely CodecMakerConfig.withAlwaysEmitDiscriminator(true), which changes the behavior to always emit the discriminator regardless of whether or not it is strictly required based upon the statically known type. Concretly, the 3 examples above would now become serialized like the following.

{
  "foo": {
    "type": "Foo",
    "x": 1,
    "y": "a string"
  }
}
{
  "bar": {
     "type": "Bar",
     "a": "a string",
     "b": 1.2
   }
}
{
  "base": {
    "type": "Foo",
    "x": 1,
    "y": "a string"
  }
}

Reading compatibility

Also note that while the serialized output above would be generated, the original json without the not-strictly-necessary discriminators would still successfully deserialize as expected.

This commit adds the `alwaysEmitDiscriminator` flag to CodecMakerConfig
in order to support always emitting the discriminator field for ADT
leaf classes. This new flag has no effect on generated decoders, and it
does not effect encoder generation for classes that are not members of
ADTs. This new flag also should have no effect on codecs derived for the
root of the ADT.
@jamie-heller
Copy link
Contributor Author

@plokhotnyuk I noticed that this is currently failing +mimaReportBinaryIssues with the following due to the addition of the new flag in CodecMakerConfig's private constructor

[error] jsoniter-scala-macros: Failed binary compatibility check against com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-macros_2.12:2.29.0! Found 1 potential problems
[error]  * method this(scala.PartialFunction,scala.PartialFunction,scala.Function1,scala.Option,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Int,Int,Int,Int,Int,Int,Int,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean)Unit in class com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig does not have a correspondent in current version
[error]    filter with: ProblemFilters.exclude[DirectMissingMethodProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.this")
[error] jsoniter-scala-macros: Failed binary compatibility check against com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-macros_sjs1_2.12:2.29.0! Found 1 potential problems
[error]  * method this(scala.PartialFunction,scala.PartialFunction,scala.Function1,scala.Option,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Int,Int,Int,Int,Int,Int,Int,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean)Unit in class com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig does not have a correspondent in current version
[error]    filter with: ProblemFilters.exclude[DirectMissingMethodProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.this")
[error] jsoniter-scala-macros: Failed binary compatibility check against com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-macros_native0.4_2.12:2.29.0! Found 1 potential problems
[error]  * method this(scala.PartialFunction,scala.PartialFunction,scala.Function1,scala.Option,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Int,Int,Int,Int,Int,Int,Int,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean,Boolean)Unit in class com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig does not have a correspondent in current version

I noticed that previously there were some filters enabled to ignore those warnings, but it looks like they have been removed -- let me know if you'd like me to approach this another way to better preserve binary compatibility or if I am going about that wrong.

@andyczerwonka
Copy link
Contributor

andyczerwonka commented May 31, 2024

This is a great feature given many API consumers will bind to a schema that always includes a type discriminator, no matter the context. Simply the fact that the type is part of a co-product hierarchy likely suggests that client libraries will expect it. We have experienced this issue. One could argue that this should even be the default, but obviously going that far would break existing users.

@plokhotnyuk
Copy link
Owner

@jamie-heller Thanks for your contribution!

To pass MiMa checks need to increase a version number to 2.30.0-SNAPSHOT that will disable checking of forward compatibility (only backward compatibility will be checked).

@jamie-heller
Copy link
Contributor Author

@jamie-heller Thanks for your contribution!

To pass MiMa checks need to increase a version number to 2.30.0-SNAPSHOT that will disable checking of forward compatibility (only backward compatibility will be checked).

Ah thanks! Just did in 6065e9c

@jamie-heller
Copy link
Contributor Author

If I'm reading this documentation correctly (related conversation HERE) I guess this does indeed introduce a backwards compatibility issue -- which explains why the +mimaReportBinaryIssues continues to fail for me locally.

@plokhotnyuk Let me know which of the following approaches you'd like me to take to resolve this and I'll be happy to make the changes.

  1. Add a new overloaded this constructor to preserve the compatibility
  2. Refactor the new alwaysEmitDiscriminator to internally use a private var in order to keep the constructor the same (but keep the same builder-style semantics)
  3. Given that this compatibility issue is restricted to the macros package, add ProblemFilters.exclude[DirectMissingMethodProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.CodecMakerConfig.this") to ignore it.

@plokhotnyuk plokhotnyuk merged commit 3da4710 into plokhotnyuk:master Jun 1, 2024
0 of 2 checks passed
@plokhotnyuk
Copy link
Owner

plokhotnyuk commented Jun 1, 2024

@jamie-heller Thanks!

I've selected 3rd option because changes in the private primary constructor cannot break backward compatibility.

I'll cut a release as soon as possible, but now I'm experiencing some hanging in the promote stage on oss.sonatype.org.

@jamie-heller jamie-heller deleted the always-emit-adt-discriminator branch June 1, 2024 12:46
@jamie-heller
Copy link
Contributor Author

@jamie-heller Thanks!

I've selected 3rd option because changes in the private primary constructor cannot break backward compatibility.

I'll cut a release as soon as possible, but now I'm experiencing some hanging in the promote stage on oss.sonatype.org.

Thanks @plokhotnyuk! I appreciate your responsiveness, and all your work on this library 🙂

@plokhotnyuk
Copy link
Owner

plokhotnyuk commented Jun 1, 2024

@jamie-heller Done

Finally, the release with your improvements is available on the Maven Central!

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

Successfully merging this pull request may close these issues.

3 participants