Сериализатор Kotlin для ReamList и List

Я только что попытался использовать библиотеку сериализации Kotlin для замены Gson, однако у меня возникли проблемы с ее использованием для RealmList в моей модели. Любая помощь будет оценена. Получаю ошибку kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class io.realm.RealmList

У меня есть класс данных, подобный этому

data class Person(
var name: String = "", 
var social : RealmList<Social> = null, 
var id: String = "") 

и мой класс социальных данных

data class Social(
var name: String = "", 
var category : String = "", 
var id: String = "")

а это мой ремонтный конструктор

fun provideRetrofit(okHttpClient: OkHttpClient, urlProvider :
    URLProvider): Retrofit {
    val contentType = MediaType.get("application/json")
    return Retrofit.Builder()
        .baseUrl(urlProvider.getApiBaseUrl()!!)
        .addConverterFactory(Json.asConverterFactory(contentType))
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .client(okHttpClient)
        .build()
}

я использую адаптер Retrofit, специфичный для сериализации Kotlin, и мой вызов модификации

override fun loadPersons(): Single<Person> {
    return communicationContext.personApi
        .getPersonsObservable()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .doOnError {
            Log.e(TAG, "Load error $it")
        }
        .doOnSuccess {
            Log.e(TAG, "Success size ${it.size}")
        }
}

person saintjab    schedule 28.10.2019    source источник
comment
Возможный дубликат проблем сериализации RealmList (Realm/Gson/Intent)   -  person Stanislav Bondar    schedule 28.10.2019
comment
Вы должны взглянуть на эту ссылку: github.com/Kotlin/kotlinx.serialization/issues /337   -  person Mr. Patel    schedule 28.10.2019
comment
@StanislavBondar, который использовал Gson, я использую сериализацию Kotlin, я не думаю, что это дубликат.   -  person saintjab    schedule 28.10.2019


Ответы (1)


Вы должны создать собственный RealmListSerializer, чтобы заставить его работать.

В текущей версии 1.0.1 kotlinx.serialization нет ListSerializer, от которого вы могли бы расшириться. Это означает, что вы должны создать свой собственный с нуля.

К счастью, в библиотеке существует своего рода ListSerializer, но он помечен как внутренний и поэтому недоступен для вас в коде. Тем не менее мне удалось написать сериализатор на основе найденных в библиотеке. Все основано на ArrayListSerializer и его родительских классах.

ОСТОРОЖНОСТЬ! Эти классы помечены как экспериментальные. Очень вероятно, что их реализация может измениться, что нарушит все поведение. Следует ожидать ошибок.

Скопированные классы: ArrayListSerializer, ListLikeSerializer, AbstractCollectionSerializer в пакете kotlinx.serialization.internal.CollectionSerializer.kt и ArrayClassDesc, ListLikeDescriptor в пакете kotlinx.serialization.internal.CollectionDescriptors.kt.

Вот код: RealmListSerializer.kt

    import io.realm.RealmList
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.SerialKind
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializer(forClass = RealmList::class)
class RealmListSerializer<E>(private val dataSerializer : KSerializer<E>) : KSerializer<RealmList<E>> {
    fun builder(): ArrayList<E> = arrayListOf()
    private fun ArrayList<E>.toResult() : RealmList<E> {
        val realmList = RealmList<E>()
        for (i in this) {
            realmList.add(i)
        }
        return realmList
    }

    private fun merge(decoder: Decoder): RealmList<E> {
        val builder = builder()
        val startIndex = builder.size
        val compositeDecoder = decoder.beginStructure(descriptor)
        if (compositeDecoder.decodeSequentially()) {
            readAll(compositeDecoder, builder, startIndex, readSize(compositeDecoder, builder))
        } else {
            while (true) {
                val index = compositeDecoder.decodeElementIndex(descriptor)
                if (index == CompositeDecoder.DECODE_DONE) break
                readElement(compositeDecoder, startIndex + index, builder)
            }
        }
        compositeDecoder.endStructure(descriptor)
        return builder.toResult()
    }

    override val descriptor : SerialDescriptor = RealmListDescriptor(dataSerializer.descriptor)

    override fun serialize(encoder : Encoder, value : RealmList<E>) {
        val size = value.size
        val composite = encoder.beginCollection(descriptor, size)
        val iterator = value.iterator()
        for (index in 0 until size)
            composite.encodeSerializableElement(descriptor, index, dataSerializer, iterator.next())
        composite.endStructure(descriptor)
    }

    override fun deserialize(decoder : Decoder) : RealmList<E> = merge(decoder)

    private fun readSize(decoder: CompositeDecoder, builder: ArrayList<E>): Int {
        val size = decoder.decodeCollectionSize(descriptor)
        builder.ensureCapacity(size)
        return size
    }

    private fun readElement(decoder: CompositeDecoder, index: Int, builder: ArrayList<E>, checkIndex: Boolean = true) {
        builder.add(index, decoder.decodeSerializableElement(descriptor, index, dataSerializer))
    }
    private fun readAll(decoder: CompositeDecoder, builder: ArrayList<E>, startIndex: Int, size: Int) {
        require(size >= 0) { "Size must be known in advance when using READ_ALL" }
        for (index in 0 until size)
            readElement(decoder, startIndex + index, builder, checkIndex = false)
    }
}

class RealmListDescriptor(private val elementDescriptor : SerialDescriptor) : SerialDescriptor {
    override val kind: SerialKind get() = StructureKind.LIST
    override val elementsCount: Int = 1

    override fun getElementName(index: Int): String = index.toString()
    override fun getElementIndex(name: String): Int =
        name.toIntOrNull() ?: throw IllegalArgumentException("$name is not a valid list index")

    override fun isElementOptional(index: Int): Boolean {
        require(index >= 0) { "Illegal index $index, $serialName expects only non-negative indices"}
        return false
    }

    override fun getElementAnnotations(index: Int): List<Annotation> {
        require(index >= 0) { "Illegal index $index, $serialName expects only non-negative indices"}
        return emptyList()
    }

    override fun getElementDescriptor(index: Int): SerialDescriptor {
        require(index >= 0) { "Illegal index $index, $serialName expects only non-negative indices"}
        return elementDescriptor
    }

    override val serialName : String
        get() = "RealmListSerializer"

}

Впоследствии вы можете использовать этот класс внутри ваших RealmObject классов следующим образом:

@Serializable
open class Person(
   
    @PrimaryKey var id: Long = 0,
    var name: String = "",
    var age: Int = 0,
@Serializable(with = RealmListSerializer::class)
    var dogs: RealmList<Dog> = RealmList()
): RealmObject()

В качестве альтернативы, если вы используете много RealmList внутри одного из ваших RealmObejct, вы можете применить это (и другие сериализаторы) ко всему файлу.

@file:UseSerializers(RealmListSerializer::class)

Опять же, пожалуйста, используйте с осторожностью. Классы, из которых он создан, все еще являются экспериментальными и могут быть изменены в любое время без предварительного уведомления.

person Shorxy    schedule 16.11.2020