#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)
), поэтому добавление аннотации, ссылающейся на сериализатор для этого класса, не имеет большого значения. Обновил свой ответ относительно сериализации сторонних интерфейсов.