Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.4k stars 621 forks source link

How to create a serializer for a `List` subclass (in particular `RealmList`) #1902

Open SuperTango opened 2 years ago

SuperTango commented 2 years ago

What is your use-case and why do you need this feature? I'm using Realm for storing data locally (on android) and JSON to transfer data over the wire. My data objects include RealmLists (which are Realm's version of List that extends AbstractList).

For example, the code looks like:

@Serializable
open class Session(): RealmObject() {
    @PrimaryKey
    @SerialName("session_id")
    var sessionId: String = ""

    @SerialName("user_ids")
    var userIds: RealmList<String>
//...
}

and the data looks like a simple List<String> (but I want to deserialize it into a RealmList<String>.

{
    "session_id": "session_23848923498",
    "user_ids": [
        "user_o823uo234u",
        "user_23i2332984",
        "user_23480192849"
    ]
}

I want to create a serializer for RealmList<String>, and I can't figure out how to do it.

I don't think I can use a composite serializer, I just want a serializer for a pseudo-primitive list type.

I thought about subclassing or copying the ListSerializer code to make a custom version that takes/returns a RealmList, bu the ListSerializer code uses many internal APIs so I that doesn't work.

Describe the solution you'd like

I'm not sure if there is a way to do this already and I'm just not seeing it, but I want to know the magic incantation to write a serializer for a List subclass (and eventually other subclass es like Map).

pdvrieze commented 2 years ago

Unfortunately it is not really possible to do this fully at this point. The ability to handle list appends is handled by type checking. An interface that would be public would allow this to work better.

However, as lists are not allowed to have extra properties for serialization anyway you should handle this by having a custom serializer/deserializer for the type containing the list (Session) where you create the desired subtype on construction, based upon the raw list. As alternative you may be able to create a serializer for RealmList that delegates to a regular ListSerializer and on deserialization creates a RealmList from the list.

SuperTango commented 2 years ago

Thanks @pdvrieze

As alternative you may be able to create a serializer for RealmList that delegates to a regular ListSerializer and on deserialization creates a RealmList from the list.

Yes, this would be great, but I'm not sure how to define this serializer.

I tried unsuccessfully to use something that looks like a primitive serializer because the description needs to have a PrimitiveSerialDescriptor, which in turn requires a PrimitiveKind

I've also tried a custom composite serializer, but (from my limited understanding) that seems to want the JSON to look like

{
    "session_id": "session_23848923498",
    "some_composite_top_level_object_name": {
        "user_ids": [
            "user_o823uo234u",
            "user_23i2332984",
            "user_23480192849"
        ]
    }
}

Which won't work with my existing JSON.

Could you give me some direction on how to create this serializer that delegates to the ListSerializer?

Thanks.

sandwwraith commented 2 years ago

I think RealmList should have some kind of asList view function, doesn't it? In this case, you can delegate to ListSerializer roughly like this:

class MyRealmListSerializer<T>(val tSer: KSerializer<T>): KSerializer<RealmList<T>> {
   fun serialize(enc: Encoder, obj: RealmList<T>) {
        enc.encodeSerializableValue(ListSerializer(tSer), obj.asList())
   }
}

Same for deserialize

SuperTango commented 2 years ago

Thanks @sandwwraith. What is the descriptor for this MyRealmListSerializer?

sandwwraith commented 2 years ago

Again, you can use SerialDescriptor(serialName: String, original: SerialDescriptor) function to wrap ListSerializer(tSer).descriptor)