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 additional properties #1978

Open
pschichtel opened this issue Jul 4, 2022 · 6 comments
Open

Support for additional properties #1978

pschichtel opened this issue Jul 4, 2022 · 6 comments
Assignees
Labels

Comments

@pschichtel
Copy link
Contributor

It is sometimes the case that I want to parse an object with a few concrete properties and a bunch of additional properties.

OpenAPI 3's Responses Object is an example for this. It has the fixed default default plus additional fields for various http statuses.

so this could look like:

{
  "default": {...},
  "200": {...},
  "400": {...},
  "404": {...}
}

And it would be great to be able to parse this into something like:

@Serializable
data class Responses(val default: Response, statuses: Map<String, Response>)

Jackson has the @JsonAnySetter annotation which can be used to implement this.

OpenAPI/JSON Schema support specifying object schemas that have both properties and additionalProperties.

Currently I think the only solution would be a hand-written KSerializer for Responses.

@pschichtel
Copy link
Contributor Author

for reference the KSerializer I wrote for my example:

object ResponsesSerializer : KSerializer<Responses> {
    private val mapSerializer = MapSerializer(String.serializer(), Response.serializer())
    private const val defaultFieldName = "default"

    override val descriptor: SerialDescriptor = object : SerialDescriptor by mapSerializer.descriptor {
        override val serialName: String = "Responses"
    }

    override fun serialize(encoder: Encoder, value: Responses) {
        val pairs = listOfNotNull(
            value.default?.let { Pair(defaultFieldName, it) },
        ) + value.statuses.map { (key, value) -> Pair(key.code.toString(), value) }

        mapSerializer.serialize(encoder, pairs.toMap())
    }

    override fun deserialize(decoder: Decoder): Responses {
        val map = mapSerializer.deserialize(decoder).toMutableMap()
        val default = map.remove(defaultFieldName)
        val statuses = map.mapKeys { (key, _) -> HttpStatusCode(key.toUShort()) }
        return Responses(default, statuses)
    }
}

@sandwwraith
Copy link
Member

See #959 (comment)

@pschichtel
Copy link
Contributor Author

Interesting alternative to my serializer, but it still requires me to write a custom serializer doesn't it?

@sandwwraith
Copy link
Member

Yes, it still requires one, but given that JsonTransformingSerializer is much easier to implement, I'm not sure that this feature needs special support

@pschichtel
Copy link
Contributor Author

I guess "require" is a strong word here. Technically it is obviously not required since there is at least two alternative approaches.

However it would be great to have a dedicated feature for that, especially since this requires duplicating field names into the transformation which can easily go out of sync with the model.

An annotation @AdditionalFields or @UnknownFields on a Map<*, *> field that then automatically collects all fields that are not consumed by other fields would be what comes to my mind.

@efemoney
Copy link

Yes, it still requires one, but given that JsonTransformingSerializer is much easier to implement, I'm not sure that this feature needs special support

The use cases for KxS are more than just JSON, would be great to have this possibly supported in custom formats

cjbooms pushed a commit to cjbooms/fabrikt that referenced this issue Nov 18, 2024
Introduces support for [kotlinx.serialization](https://kotlinlang.org/docs/serialization.html) (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](https://github.com/Kotlin/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](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual/) to instruct the plugin to resolve serializer at runtime.
Related: Kotlin/kotlinx.serialization#1978.
3. URI and UUID are translated to strings as no native Kotlin types exist ([yet](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.uuid/-experimental-uuid-api/)?).
4. `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.
@shanshin shanshin self-assigned this Jan 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants