Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.24k stars 615 forks source link

Support for additional properties #1978

Open pschichtel opened 2 years ago

pschichtel commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

See https://github.com/Kotlin/kotlinx.serialization/issues/959#issuecomment-683881720

pschichtel commented 2 years ago

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

sandwwraith commented 2 years ago

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 commented 2 years ago

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 commented 2 months ago

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