I'm using kotlinx.serialization in a KMP sdk for response parsing. I've seen that the properties module can be used to easily create the query map from a given class (I'm using interface inheritance to produce cleaner and safer code).
An example of a query class could be:
@Serializable
class MyQuery(
val latitude: QueryCollection<Float>, // alias to `@Serializable(with = ...) Collection<T>`
val variables: QueryCollection<Variables>,
)
I need to generate a ?latitude=1.0,2.0,-3.0&variables=hello,stack,overflow string from it.
I got everything, but the deserialize method working. It's not crucial to get it implemented, but I wanted to get a better understanding of the library. Unfortunately, I couldn't get the single element decoding step from String to T working.
Here's the trivial code:
class QueryCollectionSerializer<T>(
private val dataSerializer: KSerializer<T>
) : KSerializer<Collection<T>> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("QueryCollection<T>", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Collection<T> =
TODO("Deserialization not (yet) implemented")
override fun serialize(encoder: Encoder, value: Collection<T>) =
encoder.encodeString(value.joinToString(","))
}
typealias QueryCollection<T> = @Serializable(QueryCollectionSerializer::class) Collection<T>
Point 1: Is the (string) primitive kind appropriate for this situation? Should I be using a ListSerializer instead?
Point 2: I tried
- decoding one string at a time thruogh
decoder.decodeString().split(',').map { /* magic String -> T... How? Idk */ } - Using
decoder.decodeStructure. I was only able to type/semantically correct code, but it wasn't decoding any element, since I didn't know how to tell the decoder that it should get a new element after a comma
Am I missing something?
I suspect I could be forced to write a custom "Query" format by mostly copying the Properties one, but it honestly seems a bit too much for a feature that I barely anyone would be using (again, this is mostly a thing I want to understand, not just "solve for the sake of the sdk")
Implementing
deserializeI do not believe there is any "legitimate" way to implement
deserializegiven howserializehas been implemented. ThedataSerializerhas been completely bypassed due to converting the collection directly into a string. That means nothing actually knows the "structure" of the collection's elements. Even if you can reliably split the string by a delimiter, you have no way of knowing how to convert each individual string toT, let alone knowing whatTis in the first place.Descriptor
Since you've implemented
serializeto encode aString, using aPrimitiveSerialDescriptorwithPrimitiveKind.STRINGis correct.Custom Serial Formats
You are effectively trying to force a particular serial format via a
KSerializer. But the format is not the serializer's responsibility. In general, the "flow" of thekotlinx.serializationlibrary looks like:SerializationStrategyEncoder.EncoderDecoderDeserializationStrategyDecoderinto an object.KSerializerXXXStrategyinterfaces. Whenever you are creating a custom serializer, you will probably want to implement this interface (necessary if you want to specify your implementation via annotations).Note: A serializer can be composed of other serializers. This allows "nested" complex types to be serialized into primitives (e.g.,
String,Int,Boolean, etc.), and vice versa, without the "parent" serializer actually knowing how. For example, thedataSerializerin your serializer is what knows how to serialize and deserialize eachT.As you can see, the
EncoderandDecoderare responsible for the serial format. And ideally, aKSerializershould be able to work with any implementation ofEncoderandDecoder.If you want to implement serialization to a certain serial format, and there is no existing library for that format, then you will have to implement your own
Encoderand/orDecoder. The Kotlin Serialization Guide has a section on creating custom serial formats which may help, if you decide the effort is worth it. Though note custom formats are apparently still experimental.