kotlinx.serialization: 将 JSON 数组反序列化为密封 class
kotlinx.serialization: Deserialize JSON array as sealed class
我的数据是富文本格式,存储为嵌套 JSON 数组。文本标记存储字符串的明文和描述格式的注释。我想在解码时将这些嵌套 JSON 数组的特定结构映射到丰富的 Kotlin class 层次结构。
这是描述此文本编码的打字稿类型:
// Text string is an array of tokens
type Text = Array<TextToken>
// Each token is a Array[2] tuple. The first element is the plaintext.
// The second element is an array of annotations that format the text.
type TextToken = [string, Array<Annotation>]
// My question is about how to serialize/deserialize the Annotation type
// to a sealed class hierarchy.
//
// Annotations are an array where the first element is always a type discriminator string
// Each annotation type may have more elements, depending on the annotation type.
type Annotation =
| ["b"] // Text with this annotation is bold
| ["i"] // Text with this annotation is italic
| ["@", number] // User mention
| ["r", { timestamp: string, reminder: string }] // Reminder
我已经定义了一些 Kotlin classes 来使用 sealed class
表示相同的东西。这是反序列化 JSON:
后我想要的输出格式
// As JSON example: [["hello ", []], ["Jake", [["b"], ["@", 1245]]]]
data class TextValue(val tokens: List<TextToken>)
// As JSON example: ["hello ", []]
// As JSON example: ["Jake", [["b"], ["@", 1245]]]
data class TextToken(val plaintext: String, val annotations: List<Annotation>)
sealed class Annotation {
// As JSON example: ["b"]
@SerialName("b")
object Bold : Annotation()
// As JSON example: ["i"]
@SerialName("i")
object Italic : Annotation()
// As JSON example: ["@", 452534]
@SerialName("@")
data class Mention(val userId: Int)
// As JSON example: ["r", { "timestamp": "12:45pm", "reminder": "Walk dog" }]
@SerialName("r")
data class Reminder(val value: ReminderValue)
}
如何定义序列化程序?我尝试使用 JsonTransformingSerializer 定义序列化程序,但是当我尝试为我的 classes:
之一包装默认序列化程序时,出现空指针异常
@Serializable(with = TextValueSerializer::class)
data class TextValue(val tokens: List<TextToken>)
object TextValueSerializer : JsonTransformingSerializer<TextValue>(TextValue.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return JsonObject(mapOf("tokens" to element))
}
override fun transformSerialize(element: JsonElement): JsonElement {
return (element as JsonObject)["tokens"]!!
}
}
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlinx.serialization.json.JsonTransformingSerializer.<init>, parameter tSerializer
at kotlinx.serialization.json.JsonTransformingSerializer.<init>(JsonTransformingSerializer.kt)
at example.TextValueSerializer.<init>(TextValue.kt:17)
您收到的错误似乎是因为您在 TextValue 序列化程序中引用了 TextValue 序列化程序。
因为数据结构与序列化程序期望的 key:value 配对不太匹配,所以很难让它自动执行这样的操作。
对于您当前的实施,以下是您需要的,从下到上:
注解
创建一个自定义序列化程序,将 JsonArray
表示形式转换为其 Annotation
表示形式。这是通过简单地将 JsonArray
的索引映射到其相应的密封 class 表示来完成的。由于第一个索引始终是描述符,我们可以使用它来告知我们要映射到的类型。
在可能的情况下,我们可以使用自动生成的 serialziers。
[] -> Annotation.None
["b"] -> Annotation.Bold
["@", 1245] -> Annotation.Mention
...
为此,您可以创建一个新的序列化程序并将其附加到 Annotation
class (@Serializable(with = AnnotationSerializer::class)
).
object AnnotationSerializer : KSerializer<Annotation> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Annotation") {}
override fun serialize(encoder: Encoder, value: Annotation) {
val jsonEncoder = encoder as JsonEncoder
// Encode the Annotation as a json element by first converting the annotation
// to a JsonElement
jsonEncoder.encodeJsonElement(buildJsonArray {
when (value) {
is TextAnnotation.None -> {}
is TextAnnotation.Bold -> { add("b") }
is TextAnnotation.Italic -> { add("i") }
is TextAnnotation.Mention -> {
add("@")
add(value.userId)
}
is TextAnnotation.Reminder -> {
add("r")
add(jsonEncoder.json.encodeToJsonElement(ReminderValue.serializer(), value.value))
}
}
})
}
override fun deserialize(decoder: Decoder): Annotation {
val jsonDecoder = (decoder as JsonDecoder)
val list = jsonDecoder.decodeJsonElement().jsonArray
if (list.isEmpty()) {
return Annotation.None
}
return when (list[0].jsonPrimitive.content) {
"b" -> Annotation.Bold
"i" -> Annotation.Italic
"@" -> Annotation.Mention(list[1].jsonPrimitive.int)
"r" -> Annotation.Reminder(jsonDecoder.json.decodeFromJsonElement(ReminderValue.serializer(), list[1].jsonObject))
else -> throw error("Invalid annotation discriminator")
}
}
}
@Serializable(with = AnnotationValueSerializer::class)
sealed class TextAnnotation {
TextToken
TextToken
遵循同样的策略。我们首先在第一个索引处提取标记,然后使用第二个索引构建注释。如上所述,我们需要注释 TextToken
class 以使用以下 serialzier:
object TextTokenSerializer : KSerializer<TextToken> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TextToken") {}
override fun serialize(encoder: Encoder, value: TextToken) {
val jsonDecoder = encoder as JsonEncoder
jsonDecoder.encodeJsonElement(buildJsonArray {
add(value.plaintext)
add(buildJsonArray {
value.annotations.map {
add(jsonDecoder.json.encodeToJsonElement(it))
}
})
})
}
override fun deserialize(decoder: Decoder): TextToken {
val jsonDecoder = decoder as JsonDecoder
val element = jsonDecoder.decodeJsonElement().jsonArray
// Token
val plaintext = element[0].jsonPrimitive.content
// Iterate over the annotations
val annotations = element[1].jsonArray.map {
jsonDecoder.json.decodeFromJsonElement<TextAnnotation>(it.jsonArray)
}
return TextToken(plaintext, annotations)
}
}
return以下JSON可能更好:
{ plaintext: "Jake", annotations: [["b"], ["@", 1245]] }
将更好地映射到 TextToken
POJO,并且不再需要序列化程序。
文本值
拼图的最后一块是 TextValue 对象,它有效地包装了 TextTokens 列表。最好为此使用以下类型别名:
typealias TextValue = List<TextToken>
在当前模型中,您可以使用序列化程序将 JsonArray
解析为 List<TextToken>
,然后将该列表包装在 TextValue
对象中。
object TextValueSerializer : KSerializer<TextValue> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TextValue") {}
override fun serialize(encoder: Encoder, value: TextValue) {
val jsonEncoder = (encoder as JsonEncoder)
jsonEncoder.encodeSerializableValue(ListSerializer(TextToken.serializer()), value.tokens)
}
override fun deserialize(decoder: Decoder): TextValue {
val jsonDecoder = decoder as JsonDecoder
val list = jsonDecoder.decodeJsonElement().jsonArray
return TextValue(list.map { jsonDecoder.json.decodeFromJsonElement(it.jsonArray) })
}
}
我的数据是富文本格式,存储为嵌套 JSON 数组。文本标记存储字符串的明文和描述格式的注释。我想在解码时将这些嵌套 JSON 数组的特定结构映射到丰富的 Kotlin class 层次结构。
这是描述此文本编码的打字稿类型:
// Text string is an array of tokens
type Text = Array<TextToken>
// Each token is a Array[2] tuple. The first element is the plaintext.
// The second element is an array of annotations that format the text.
type TextToken = [string, Array<Annotation>]
// My question is about how to serialize/deserialize the Annotation type
// to a sealed class hierarchy.
//
// Annotations are an array where the first element is always a type discriminator string
// Each annotation type may have more elements, depending on the annotation type.
type Annotation =
| ["b"] // Text with this annotation is bold
| ["i"] // Text with this annotation is italic
| ["@", number] // User mention
| ["r", { timestamp: string, reminder: string }] // Reminder
我已经定义了一些 Kotlin classes 来使用 sealed class
表示相同的东西。这是反序列化 JSON:
// As JSON example: [["hello ", []], ["Jake", [["b"], ["@", 1245]]]]
data class TextValue(val tokens: List<TextToken>)
// As JSON example: ["hello ", []]
// As JSON example: ["Jake", [["b"], ["@", 1245]]]
data class TextToken(val plaintext: String, val annotations: List<Annotation>)
sealed class Annotation {
// As JSON example: ["b"]
@SerialName("b")
object Bold : Annotation()
// As JSON example: ["i"]
@SerialName("i")
object Italic : Annotation()
// As JSON example: ["@", 452534]
@SerialName("@")
data class Mention(val userId: Int)
// As JSON example: ["r", { "timestamp": "12:45pm", "reminder": "Walk dog" }]
@SerialName("r")
data class Reminder(val value: ReminderValue)
}
如何定义序列化程序?我尝试使用 JsonTransformingSerializer 定义序列化程序,但是当我尝试为我的 classes:
之一包装默认序列化程序时,出现空指针异常@Serializable(with = TextValueSerializer::class)
data class TextValue(val tokens: List<TextToken>)
object TextValueSerializer : JsonTransformingSerializer<TextValue>(TextValue.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return JsonObject(mapOf("tokens" to element))
}
override fun transformSerialize(element: JsonElement): JsonElement {
return (element as JsonObject)["tokens"]!!
}
}
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlinx.serialization.json.JsonTransformingSerializer.<init>, parameter tSerializer
at kotlinx.serialization.json.JsonTransformingSerializer.<init>(JsonTransformingSerializer.kt)
at example.TextValueSerializer.<init>(TextValue.kt:17)
您收到的错误似乎是因为您在 TextValue 序列化程序中引用了 TextValue 序列化程序。
因为数据结构与序列化程序期望的 key:value 配对不太匹配,所以很难让它自动执行这样的操作。
对于您当前的实施,以下是您需要的,从下到上:
注解
创建一个自定义序列化程序,将
JsonArray
表示形式转换为其Annotation
表示形式。这是通过简单地将JsonArray
的索引映射到其相应的密封 class 表示来完成的。由于第一个索引始终是描述符,我们可以使用它来告知我们要映射到的类型。在可能的情况下,我们可以使用自动生成的 serialziers。
[] -> Annotation.None ["b"] -> Annotation.Bold ["@", 1245] -> Annotation.Mention ...
为此,您可以创建一个新的序列化程序并将其附加到
Annotation
class (@Serializable(with = AnnotationSerializer::class)
).object AnnotationSerializer : KSerializer<Annotation> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Annotation") {} override fun serialize(encoder: Encoder, value: Annotation) { val jsonEncoder = encoder as JsonEncoder // Encode the Annotation as a json element by first converting the annotation // to a JsonElement jsonEncoder.encodeJsonElement(buildJsonArray { when (value) { is TextAnnotation.None -> {} is TextAnnotation.Bold -> { add("b") } is TextAnnotation.Italic -> { add("i") } is TextAnnotation.Mention -> { add("@") add(value.userId) } is TextAnnotation.Reminder -> { add("r") add(jsonEncoder.json.encodeToJsonElement(ReminderValue.serializer(), value.value)) } } }) } override fun deserialize(decoder: Decoder): Annotation { val jsonDecoder = (decoder as JsonDecoder) val list = jsonDecoder.decodeJsonElement().jsonArray if (list.isEmpty()) { return Annotation.None } return when (list[0].jsonPrimitive.content) { "b" -> Annotation.Bold "i" -> Annotation.Italic "@" -> Annotation.Mention(list[1].jsonPrimitive.int) "r" -> Annotation.Reminder(jsonDecoder.json.decodeFromJsonElement(ReminderValue.serializer(), list[1].jsonObject)) else -> throw error("Invalid annotation discriminator") } } }
@Serializable(with = AnnotationValueSerializer::class) sealed class TextAnnotation {
TextToken
TextToken
遵循同样的策略。我们首先在第一个索引处提取标记,然后使用第二个索引构建注释。如上所述,我们需要注释TextToken
class 以使用以下 serialzier:object TextTokenSerializer : KSerializer<TextToken> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TextToken") {} override fun serialize(encoder: Encoder, value: TextToken) { val jsonDecoder = encoder as JsonEncoder jsonDecoder.encodeJsonElement(buildJsonArray { add(value.plaintext) add(buildJsonArray { value.annotations.map { add(jsonDecoder.json.encodeToJsonElement(it)) } }) }) } override fun deserialize(decoder: Decoder): TextToken { val jsonDecoder = decoder as JsonDecoder val element = jsonDecoder.decodeJsonElement().jsonArray // Token val plaintext = element[0].jsonPrimitive.content // Iterate over the annotations val annotations = element[1].jsonArray.map { jsonDecoder.json.decodeFromJsonElement<TextAnnotation>(it.jsonArray) } return TextToken(plaintext, annotations) } }
return以下JSON可能更好:
{ plaintext: "Jake", annotations: [["b"], ["@", 1245]] }
将更好地映射到TextToken
POJO,并且不再需要序列化程序。文本值
拼图的最后一块是 TextValue 对象,它有效地包装了 TextTokens 列表。最好为此使用以下类型别名:
typealias TextValue = List<TextToken>
在当前模型中,您可以使用序列化程序将
JsonArray
解析为List<TextToken>
,然后将该列表包装在TextValue
对象中。object TextValueSerializer : KSerializer<TextValue> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TextValue") {} override fun serialize(encoder: Encoder, value: TextValue) { val jsonEncoder = (encoder as JsonEncoder) jsonEncoder.encodeSerializableValue(ListSerializer(TextToken.serializer()), value.tokens) } override fun deserialize(decoder: Decoder): TextValue { val jsonDecoder = decoder as JsonDecoder val list = jsonDecoder.decodeJsonElement().jsonArray return TextValue(list.map { jsonDecoder.json.decodeFromJsonElement(it.jsonArray) }) } }