I'm implementing KotlinX.Serialization support for...
# serialization
c
I'm implementing KotlinX.Serialization support for GeoJSON (RFC 7946). I'm primarily focused on my custom BSON format and the JSON format. Fundamentally, each type is very similar, but has different coordinates. `Point`:
Copy code
{
  "type": "Point",
  "coordinates": [
    5.2,
    7.3
  ]
}
Polygon
:
Copy code
{
  "type": "Polygon",
  "coordinates": [
    [
      [
        0,
        0
      ],
      [
        10,
        0
      ],
      [
        10,
        10
      ],
      [
        0,
        10
      ],
      [
        0,
        0
      ]
    ],
    [
      [
        2,
        2
      ],
      [
        8,
        2
      ],
      [
        8,
        8
      ],
      [
        2,
        8
      ],
      [
        2,
        2
      ]
    ]
  ]
}
(and 5 other types that follow the same logic) I implemented each type separately using surrogates. However, I also need a polymorphic serializer that uses the
type
field to decide which to decide which is which. I don't know how to write that serializer.
h
Just use a sealed interface?
c
Sure, I have one, but the default serializer doesn't generate that structure. It tries to do
Copy code
{
    "type": "Point",
    "value": {
        "type": "Point",
        "coordinates": [ … ]
    }
}
For the serializer, I can do:
Copy code
@LowLevelApi
	object Serializer : KSerializer<Geo> {
		@OptIn(InternalSerializationApi::class)
		override val descriptor: SerialDescriptor
			get() = buildSerialDescriptor("opensavvy.ktmongo.bson.types.Geo", SerialKind.CONTEXTUAL) {
				element("Point", Point.serializer().descriptor)
				element("LineString", LineString.serializer().descriptor)
				element("Polygon", Polygon.serializer().descriptor)
				element("MultiPoint", MultiPoint.serializer().descriptor)
				element("MultiLineString", MultiLineString.serializer().descriptor)
				element("MultiPolygon", MultiPolygon.serializer().descriptor)
			}

		override fun serialize(encoder: Encoder, value: Geo) {
			when (value) {
				is Point -> encoder.encodeSerializableValue(Point.serializer(), value)
				is LineString -> encoder.encodeSerializableValue(LineString.serializer(), value)
				is Polygon -> encoder.encodeSerializableValue(Polygon.serializer(), value)
				is MultiPoint -> encoder.encodeSerializableValue(MultiPoint.serializer(), value)
				is MultiLineString -> encoder.encodeSerializableValue(MultiLineString.serializer(), value)
				is MultiPolygon -> encoder.encodeSerializableValue(MultiPolygon.serializer(), value)
			}
		}

		override fun deserialize(decoder: Decoder): Geo {
			TODO("Not yet implemented")
		}
	}
No idea how to write the deserializer though.
e
c
@ephemient that only works for JSON, for other formats it tries to put the value in a nested object:
Copy code
{
    "type": "Point",
    "value": {
        "type": "Point",
        "coordinates": [ … ]
    }
}
e
kotlinx.serialization doesn't support json-style polymorphism in any of its other formats, but if you're developing a custom bson format then you could support it yourself
c
It will still be formatted incorrectly if users use yet another format, e.g. CBOR.
e
well afaik there's no way to do what you want with the existing kotlinx.serialization.cbor so blob shrug
c
I don't get why it's so difficult to conditionally deserialize based on the value of a field.
e
it's not that it's technically difficult, but the API isn't shaped to enable it. related https://github.com/Kotlin/kotlinx.serialization/issues/1573
➖ 1
c
Interesting read
However, I'm not sure I need all of this? I know what the possible structures are, I just don't know which one it is. It's really exactly what the default serializer is doing, but with slightly different structure.
But I also do have my own ‘arbitrary element’ type so maybe I should deserialize into that and then write checks myself.
e
I have this already. In my case its for json but should work just the same for custom formats also.
The summary is that you’ll “keep the generated serializer” but mark the wrapper type as polymorphic. The generated serializer will ofc be parameterized by the actual polymorphic parts. The actual serializer can then be looked up from the module as polymorphic and you can either use a mapping of type -> serializer, like I did, OR delegate to an existing sealed hierarchy KSerializer.