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

Support for Kotlin Serialization #327

Merged
merged 18 commits into from
Nov 18, 2024

Conversation

ulrikandersen
Copy link
Collaborator

@ulrikandersen ulrikandersen commented Oct 17, 2024

This PR builds on the ideas in #249 (tracked in #89) to introduce support for kotlinx.serialization (KxS) annotations on the generated models.

A new --serialization-library option allows the user to choose between JACKSON and KOTLINX_SERIALIZATION.
The Jackson implementation is the default and has not changed, and users will get the same output as previously.

For date and datetime kotlinx-datetime is used to make the generated code work in multi platform / non-JVM environments. Date is translated to kotlinx.datetime.LocalDate and datetime to kotlinx.datetime.Instant.

Unsupported features

  1. When KxS is chosen additionalProperties: true is not supported. This is due to fact that supporting a map<String,Any?> is quite tricky (if at all possible?) with Kotlinx.Serialization, whereas Jackson has JsonAnyGetter/JsonAnySetter to support this.
    We might be able to configure the property with @Contextual to instruct the plugin to resolve serializer at runtime.
    Related: Support for additional properties Kotlin/kotlinx.serialization#1978.
  2. URI and UUID are translated to strings as no native Kotlin types exist (yet?).
  3. number without format is currently translated to Java's BigDecimal which will not work in non-JVM Kotlin. Works fine for number/float and number/double as these are translated into Kotlin types.

Tests
I am unsure how many more tests for the examples we want. Many of the examples are concerned with translating OpenAPI into models, and are not specific to the serialization annotations. Any opinions/input/suggestion are welcome.

TODO

  • Add support for basic models.
  • Add support for enums.
  • Add support for discriminated oneOf.
  • Look into how to support fields that are currently output as Java's UUID, OffsetDateTime, etc.
  • Add tests for some examples.
  • Add end-2-end tests.
  • Rename JacksonModelGenerator to simply ModelGenerator (in future PR).

@ulrikandersen ulrikandersen self-assigned this Oct 17, 2024
@ulrikandersen ulrikandersen changed the title Support Kotlin Serialization Support for Kotlin Serialization Oct 17, 2024
@ulrikandersen ulrikandersen marked this pull request as ready for review November 10, 2024 19:50
@ulrikandersen
Copy link
Collaborator Author

ulrikandersen commented Nov 10, 2024

@cjbooms Since this is a new feature, I am thinking that once you (and possibly others) have reviewed the implementation, it will be beneficial to get feedback from people that make use of Kotlin Serialization in their projects - both backend and mobile.

Since it does not change existing code generation output I think we can safely be released once we are happy with the implementation. Possibly as a "beta" release, if you are more comfortable with that.

@cjbooms
Copy link
Owner

cjbooms commented Nov 10, 2024

@cjbooms Since this is a new feature, I am thinking that once you (and possibly others) have reviewed the implementation, it will be beneficial to get feedback from people that make use of Kotlin Serialization in their projects - both backend and mobile.

Since it does not change existing code generation output I think we can safely be released once we are happy with the implementation. Possibly as a "beta" release, if you are more comfortable with that.

Nice work, this will be a great addition. I look forward to reviewing this tomorrow or the next day.

Yeah, I'm not worried about doing anything special for release. Let's just throw it out there and see what the feedback is. We're not going to break anyone, and can easily improve things in future releases as it gets used.

@ulrikandersen
Copy link
Collaborator Author

ulrikandersen commented Nov 12, 2024

It is worth noting that, due to the requirement of serializers being present at compile time, the generated Event does actually not compile. This is due to the untyped object data being generated as Map<String, Any?>.

In order for this to work, we need to annotate Any with @Contextual, which will enable (require) the user to provide a custom serializer for Any, which will be resolved at runtime.

I have made a PoC over here, but it requires polishing before being mergeable. It does not have to block this PR, but it is useful info to have in the review process.

Copy link
Owner

@cjbooms cjbooms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work. Impressed with how little impact on the existing generation code structures you managed to achieve.
A few nit picks and clarifications for my own benefit, but I'm happy to release and get community feedback if that's what you want.

@@ -67,13 +68,15 @@ import java.io.Serializable
import java.net.MalformedURLException
import java.net.URL

class JacksonModelGenerator(
class JacksonModelGenerator( // TODO: Rename to ModelGenerator
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go for it!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Will do in a PR once this has been merged.

import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec

sealed interface SerializationAnnotations {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice abstraction. I can imagine you had lots of nasty branching logic before you introduced it. kudos

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zettelmj gave me the idea in #249 and from there it was pretty smooth 🙌

}
OasType.DateTime -> {
if (MutableSettings.serializationLibrary() == KOTLINX_SERIALIZATION) KotlinxInstant
else getOverridableDateTimeType()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does kotlinx only have one date-time, or may we also need an overridable function for it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only one and you would normally use Instant for datetimes and thus no need for overrides ✌️

public val entityId: String,
@SerialName("data")
@get:NotNull
public val `data`: Map<String, Any?>,
Copy link
Owner

@cjbooms cjbooms Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrt your pre-review comment around @Contextual, are we being inconsistent by generating this, but omitting additional properties? I'm wondering would it be better to error out, rather than produce code that:
a.) Doesn't compile
b.) Results in data loss (is it possible that kotlinx will quietly drop additional properties?)

Or maybe we should add the annotation to give the user a better hint to implement the required serializer?

Also, do you have any idea if there is a KotlinX issue we can track for this feature to be natively supported?

Copy link
Collaborator Author

@ulrikandersen ulrikandersen Nov 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets be consistent and error out in case untyped objects or additional properties are found in the schema. I have added this to the PR.

Supporting untyped objects with @Contextual is totally viable, but we need to figure out how to build it in a user friendly way.

Support for additionalProperties is being tracked in this issue by @pschichtel.

@ulrikandersen
Copy link
Collaborator Author

@cjbooms Thanks for performing an initial review 🙏 Once you’ve had a chance to go through the changes, I’d love to get this released so we can hear what the community thinks.

@cjbooms cjbooms merged commit 22944d3 into cjbooms:master Nov 18, 2024
1 check passed
@ulrikandersen ulrikandersen deleted the kotlinx-serialization branch December 20, 2024 11:55
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.

2 participants