Stored as nested JSON arrays, my data is in rich text format. The plaintext of the string and annotations describing the formatting are stored in text tokens. At decode time, I aim to map the specific structure of these nested JSON arrays to a rich Kotlin class hierarchy.
The TypeScript type below describes this text encoding:
// 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
To represent the same thing using Kotlin classes with sealed class
, I have defined the following structured output format after deserializing the 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)
}
How should I define my serializers? I attempted defining a serializer using JsonTransformingSerializer, but encountered a null pointer exception when trying to wrap the default serializer for one of my 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)