realm-kotlin icon indicating copy to clipboard operation
realm-kotlin copied to clipboard

Failed to decode RealmList from primitive array

Open grigorevp opened this issue 3 years ago • 5 comments

I'd like to store an array of primitives in my Realm object, so I am declaring it like:

@Serializable
class Some: RealmObject {

    @PrimaryKey
    var id: String = uuid4().toString()

    private var list: RealmList<Int> = realmListOf()

}

When trying to decode it from the server response I get Expected class kotlinx.serialization.json.JsonObject as the serialized body of kotlinx.serialization.Polymorphic<RealmList>, but had class kotlinx.serialization.json.JsonArray, which is gone in case I mark this list property as @Transient.

I am using Ktor with Json serializer:

val httpClient = HttpClient {
    expectSuccess = true
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
            encodeDefaults = true
            prettyPrint = true
        })
    }
    install(HttpTimeout) {
        requestTimeoutMillis = 15000
    }
}

Am I missing something? Or RealmList needs a custom serializer to be specified for the class which is using it?

grigorevp avatar Jul 04 '22 22:07 grigorevp

Hi @grigorevp

You can add a custom serializer for the RealmList as follow

object RealmListSerializer  : KSerializer<RealmList<String>> {
    override fun deserialize(decoder: Decoder): RealmList<String> {
        val list = realmListOf<String>()
        val element: JsonElement = (decoder as JsonDecoder).decodeJsonElement()
        for (i in 0 until element.jsonArray.size) {
            list.add(element.jsonArray[i].jsonPrimitive.content)
        }

        return list
    }

    override fun serialize(encoder: Encoder, value: RealmList<String>) {
        TODO("Not yet implemented")
    }

    override val descriptor: SerialDescriptor
        get() = PrimitiveSerialDescriptor("RealmList<String>", PrimitiveKind.STRING)

}

You can then annotate the field like

@Serializable
class Book : RealmObject {
    @SerialName("author_name")
    @Serializable(with = RealmListSerializer::class)
    var authors: RealmList<String> = realmListOf()
}

nhachicha avatar Jul 05 '22 17:07 nhachicha

Yeah, thanks! Just wanted to check if I'm not missing some built-in solution

grigorevp avatar Jul 07 '22 20:07 grigorevp

Hi @nhachicha! Need your help again, now I am trying to serialize a RealmList of RealmOject's, and am getting the following exception:

Class 'ManagedRealmList' is not registered for polymorphic serialization in the scope of 'RealmList'.

I have the following setup:

@Serializable
class Some: RealmObject {

    @PrimaryKey
    var id: String = uuid4().toString()

    var cardHolder: CardHolder? = CardHolder()

}
@Serializable
class CardHolder: RealmObject {

    @PrimaryKey
    var id: String = uuid4().toString()

    var cards: RealmList<Card> = realmListOf(Card())

}
@Serializable
class Card: RealmObject {

    @PrimaryKey
    var id: String = uuid4().toString()

    var items: RealmList<Item> = realmListOf(Item())

}
@Serializable
class Item: RealmObject {

    @PrimaryKey
    var id: String = uuid4().toString()

}

Am I doing anything wrong? Seems I won't be able to make anything with ManagerRealmList serialization, as it is not exposed

grigorevp avatar Jul 13 '22 22:07 grigorevp

Or will you advise to keep realm's layer separate to network one and provide some mapping to custom classes (which in this case seems strange for me)?

grigorevp avatar Jul 13 '22 22:07 grigorevp

Hi @grigorevp I think you need to build a custom serializer for each type and combine them... example

// Decode a RealmList of Item
object RealmListItemSerializer  : KSerializer<RealmList<String>> {
// similar to https://github.com/realm/realm-kotlin/issues/920#issuecomment-1175305134
...
}

// Decode Card
object CardSerializer : KSerializer<Card>> {

    override val descriptor: SerialDescriptor
        get() = buildClassSerialDescriptor("my.package.Card") {
            element<String>("id") // kotlinx.serialization.descriptors.element
            element<RealmList<Item>>("items")
        }

    override fun deserialize(decoder: Decoder): Card {
        return decoder.decodeStructure(descriptor) {
            val idField = decodeStringElement(descriptor, 0)
            val itemsField: RealmList<Item> =
                decodeSerializableElement(descriptor, 1, RealmListItemSerializer)
            Card().apply { id = idField; items = itemsField }
        }
    }

    override fun serialize(encoder: Encoder, value: Card) {
        TODO("Not yet implemented")
    }
}

// Similarly decode CardHolder using CardSerializer
object CardHolderSerializer : KSerializer<CardHolder>> {

    override val descriptor: SerialDescriptor
        get() = buildClassSerialDescriptor("my.package.CardHolder") {
            element<String>("id") 
            element<RealmList<Card>>("cards")
        }

    override fun deserialize(decoder: Decoder): CardHolder {
        return decoder.decodeStructure(descriptor) {
            val idField = decodeStringElement(descriptor, 0)
            val cardsField: RealmList<Card> =
                decodeSerializableElement(descriptor, 1, CardSerializer)
            CardHolder().apply { id = idField; card = cardsField }
        }
    }

    override fun serialize(encoder: Encoder, value: CardHolder) {
        TODO("Not yet implemented")
    }
}

Then apply the custom deserializer

@Serializable
class Some: RealmObject {

    @PrimaryKey
    var id: String = uuid4().toString()

   @Serializable(with = CardHolderSerializer::class)
    var cardHolder: CardHolder? = CardHolder()

}

nhachicha avatar Jul 14 '22 06:07 nhachicha

Closing due to lack of feedback. @grigorevp If you still need assistance with this please let us know.

rorbech avatar Sep 26 '22 07:09 rorbech