#json #kotlin #generics #annotations #kotlinx.serialization
Вопрос:
Сериализация Котлина-это сложно! Как мне заставить Котлина поверить, что значения на моей карте свойств являются либо примитивами, либо классами с аннотациями @Serializable
?
Я пытаюсь превратить такой класс: class Entity(val id: String, val type: String, val properties: Map<String, *>)
где я знаю, что *
это примитивно или @Serializable
или String
в JSON, например: { id: "0", type: "falcon", max-flight-range-nm: 10 }
import kotlinx.serialization.*
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.encodeToJsonElement
fun main() {
val json = Json {
prettyPrint = true
}
val falcon = Entity("0", "falcon", mapOf("max-flight-range-nm" to 10))
println(json.encodeToString(falcon))
// Desired JSON (no nesting of 'Entity.parameters', they bump-up into parent intentionally
// { id: "0", type: "falcon", max-flight-range-nm: 10 }
// Avoiding extra nesting is conceptually similar to Jackson's @JsonAnyGetter annotation: https://www.baeldung.com/jackson-annotations#1-jsonanygetter
}
@Serializable
class Entity(val id: String, val type: String, @Contextual val properties: Map<String, *>) {
@Serializer(forClass = Entity::class)
companion object : KSerializer<Entity> {
override fun serialize(encoder: Encoder, value: Entity) {
encoder.encodeString(value.id)
encoder.encodeString(value.type)
// attempted hack to encode properties at this level
for ((mapKey, mapValue) in value.properties) {
val valueJson = Json.encodeToJsonElement(mapValue)
val jsonObject = JsonObject(mapOf(mapKey to valueJson))
encoder.encodeString(jsonObject.toString())
}
}
}
}
У меня есть полный контроль над всем кодом, поэтому при необходимости я мог бы зайти так далеко, чтобы написать кучу sealed class PropertyValue
подклассов и пометить их как @Serializable
— так IntPropertyValue
, StringPropertyValue
, FooPropertyValue
, и т. Д. Может быть, это единственный выход?
К сожалению, вышеперечисленное не удается во время компиляции (хотя в Intelli-J ошибок нет).: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Serializer for element of type Any? has not been found.
Комментарии:
1. Если это невозможно, я вернусь к объектному изображению GSON или Джексона.
2. Проблема не в том, чтобы заставить сериализацию kotlinx во что-то верить; она «верит» в то, что вы пытаетесь сделать. Он говорит вам, правильно, он не знает, каких типов ожидать или как их различать. Вам нужна полиморфная сериализация, которая довольно хорошо документирована. Подумайте о том, чтобы написать ручной синтаксический анализатор JSON: как бы вы узнали, в каком виде сериализовать значения на карте? Что библиотека просит вас сделать, так это определить механизм. Использование полиморфной сериализации с помощью дискриминатора классов является распространенным подходом, но другие описаны документально.
Ответ №1:
У меня есть полный контроль над всем кодом, поэтому при необходимости я мог бы зайти так далеко, чтобы написать кучу закрытых подклассов класса PropertyValue и пометить их как @Serializable — так что IntPropertyValue, StringPropertyValue, FooPropertyValue и т. Д. Может быть, это единственный выход?
Вам нужно сделать PropertyValue
это самому @Serializable
(см. https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md) и, конечно, используйте val properties: Map<String, PropertyValue>
вместо val properties: Map<String, *>
(так как в сообщении об ошибке говорится «Сериализатор для элемента типа Any?» возможно, вы забыли этот шаг).
Также обратите внимание, что по умолчанию вы не получите
{ id: "0", type: "falcon", max-flight-range-nm: 10 }
но, например (опуская ключевые кавычки)
{ id: "0", type: "falcon", properties: {
max-flight-range-nm: {
type: "your_package.IntPropertyValue", value: 10
}
} }
Если вы хотите проанализировать/создать этот конкретный JSON, напишите пользовательский сериализатор, и тогда вам тоже может сойти с Map<String, *>
рук (но это все равно обычно плохая идея).