Как зарегистрировать адаптер глобального типа для интерфейса, не аннотируя каждый класс, при использовании kotlinx.сериализация?

#kotlin #gson #kotlinx.serialization

Вопрос:

Gson позволил бы мне сделать GsonBuilder().registerTypeAdapter(MyInterface::class.java, MyConcreteClassAdapter()) это, но я не могу сделать то же самое с kotlinx.serialization

Я хочу предоставить только интерфейс ( ValueInterface ), чтобы я мог скрыть детали реализации и сериализации. Однако этот интерфейс является полем для многих классов, например Box , и я не хочу утекать сведения о сериализации , аннотируя каждое ValueInterface поле @Serializable(with = SomeDeserializer::class)

Обратите внимание, что ValueObject у него есть своя собственная сериализация.

Следующий код разрывается с kotlinx.serialization.SerializationException: Class 'ValueObject' is not registered for polymorphic serialization in the scope of 'ValueInterface'. Mark the base class as 'sealed' or register the serializer explicitly. .

Однако , когда я изменяю Box тип значения на ValueObject вместо ValueInterface , это работает.

Что мне нужно изменить, чтобы это работало Gson и могло иметь поля типа ValueInterface ?

 package kxs

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import org.junit.Assert
import org.junit.Test

class KXSTest {

    @Test
    fun test(){
        val actualStr = Json.encodeToString(Box("name1",ValueInterface.create(42)))
        Assert.assertEquals("""{"name":"name1","value":42}""",actualStr)

        val actualObj: Box = Json.decodeFromString("""{"name":"name2","value":43}""")
        Assert.assertEquals(Box("name2",ValueInterface.create(43)),actualObj)

    }
}

// public stuff
interface ValueInterface {
    fun value() : Long

    companion object {
        fun create(long: Long) =
            ValueObject(long)
    }
}

@Serializable
data class Box(val name: String, val value: ValueInterface)


//internal details not meant to be exposed
@Serializable(with = ValueObjectAsLong::class)
data class ValueObject(val value: Long): ValueInterface {
    init {
        require(value > 0)
    }
    override fun value(): Long = value
}

object ValueObjectAsLong : KSerializer<ValueObject> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ValueObjectAsLong",PrimitiveKind.LONG)

    override fun deserialize(decoder: Decoder): ValueObject {
        return ValueObject(decoder.decodeLong())
    }

    override fun serialize(encoder: Encoder, value: ValueObject) {
        encoder.encodeLong(value.value)
    }
}

 

Ответ №1:

Чтобы обработать сериализацию ValueInterface полей, просто переместите @Serializable(with = ValueObjectAsLong::class) аннотацию из ValueObject класса в ValueInterface . Невозможно аннотировать интерфейсы без параметров @Serializable , но если вы укажете значение with параметра, он будет отлично компилироваться:

 @Serializable(with = ValueObjectAsLong::class)
interface ValueInterface { /*...*/ }
 

Это немного сложнее, если вам нужно сериализовать сторонний интерфейс.
Прежде всего, вам нужно определить сериализатор для интерфейса, а не его реализующий класс:

 object ValueInterfaceAsLong : KSerializer<ValueInterface> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ValueInterfaceAsLong", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: ValueInterface) = encoder.encodeLong(value.value())
    override fun deserialize(decoder: Decoder) : ValueInterface = ValueObject(decoder.decodeLong())
}
 

Затем добавьте @file:UseSerializers(ValueInterfaceAsLong::class) аннотацию во все файлы с объявлением сериализуемых классов с помощью ValueInterface поля.

Комментарии:

1. Да, это работает, но имеет 2 проблемы: он пропускает детали реализации и может быть выполнен только в моих собственных исходных классах. Что мне действительно нужно, так это способ регистрации сериализатора для интерфейса (который я не могу или не хочу изменять).

2. На самом деле, детали реализации интерфейса уже просочились — create() метод не только использует ValueObject класс внутренне, но и в качестве возвращаемого типа (чтобы избежать последнего, он должен был быть объявлен как fun create(long: Long): ValueInterface = ValueObject(long) ), поэтому добавление аннотации, ссылающейся на сериализатор для этого класса, не имеет большого значения. Обновил свой ответ относительно сериализации сторонних интерфейсов.