Primitive types
Kotlin serialization has the following ten native types, Boolean, Byte, Short, Int, Long, Float, Double, Char, String, and enums. The other types in Kotlin’s serialization are composite and consist of these primitive values. Raw values can be used directly for serialization.
Forget it, you still directly read the original document, there is nothing to say, are some explanatory text, very easy to understand: github.com/Kotlin/kotl…
I want to talk about custom serializers.
A format like JSON can control encoding an object into a specific output byte, but how the object is decomposed into its constituent properties is controlled by the serializer. So far, we have been using automatically derived serializers by using the @Serializable annotation or using the built-in serializer shown in “Built-in Classes.”
Start with a simple example
@Serializable
class Color(val rgb: Int)
fun main() {
val green = Color(0x00ff00)
println(Json.encodeToString(green))//{"rgb":65280}
val colorSerializer: KSerializer<Color> = Color.serializer()
println(colorSerializer.descriptor)//Color(rgb: kotlin.Int)
}
Copy the code
Each class marked @Serializable (such as the Color class in the previous example) gets an instance of the KSerializer interface automatically generated by the Kotlin serialization compiler plug-in. We can retrieve this instance using the.serializer () function on the companion object of the class. We can examine its descriptor properties that describe the structure of the serialized class.
For generic classes, such as the Box class shown in the “Generic Classes” section, the automatically generated.serializer () function takes as many arguments as the type arguments in the corresponding class. These parameters are of type KSerializer, so you must provide a serializer for the actual type parameters when constructing instances of the serializer for the generic class
@Serializable
@SerialName("Box")
class Box<T>(val contents: T)
fun main() {
val boxedColorSerializer = Box.serializer(Color.serializer())
println(boxedColorSerializer.descriptor)//Box(contents: Color)
}
fun main() {
val intSerializer: KSerializer<Int> = Int.serializer()
println(intSerializer.descriptor)
}
Copy the code
Handwritten composite serializer
In some cases, alternative solutions are not appropriate. Maybe we want to avoid the performance impact of extra allocation, or maybe we want a set of configurable/dynamic properties for the resulting serial representation. In these cases, we need to manually write a class serializer that mimics the behavior of the generated serializer.
Let’s go through them one by one. First, use buildClassSerialDescriptor builder defined descriptor. The Element function in the builder DSL automatically gets the serializer for the corresponding field based on the type. The order of the elements is important. They are indexed from zero.
We then write the serialization function using the encodeStructure DSL, which provides access to the CompositeEncoder block. The difference between Encoder and CompositeEncoder is that the latter has an encodeXxxElement function corresponding to the encodeXxx function of the former. They must be called in the same order as in the descriptor.
The most complex piece of code is the deserialization function. It must support a format that can decode properties in any order, such as JSON. It starts with a call to encodeStructure to access the CompositeDecoder. In it, we wrote a loop that repeatedly calls decodeElementIndex to decode the index of the next element, and then decodeIntElement in this example to decode the corresponding element, The loop is finally terminated when a CompositeDecoder.decode_done is encountered.
object ColorAsObjectSerializer : KSerializer<Color> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Color") { element<Int>("r") element<Int>("g") element<Int>("b") } override fun serialize(encoder: Encoder, value: Color) = encoder.encodeStructure(descriptor) { encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff) encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff) encodeIntElement(descriptor, 2, value.rgb and 0xff) } override fun deserialize(decoder: Decoder): Color = decoder.decodeStructure(descriptor) { var r = -1var g = -1var b = -1while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> r = decodeIntElement(descriptor, 0) 1 -> g = decodeIntElement(descriptor, 1) 2 -> b = decodeIntElement(descriptor, 2) CompositeDecoder.DECODE_DONE -> breakelse -> error("Unexpected index: $index") } } require(r in 0.. 255 && g in 0.. 255 && b in 0.. 255) Color((r shl 16) or (g shl 8) or b) } } @Serializable(with = ColorAsObjectSerializer::class) data class Color(val rgb: Int) fun main() { val color = Color(0x00ff00) val string = Json.encodeToString(color) println(string)//{"r":0,"g":255,"b":0} require(Json.decodeFromString<Color>(string) == color) }Copy the code
The following is a variation I wrote to mimic the official example. The main point is that the actual attributes in the class can be different from the types in the Json string, which can be restored when deserialized.
@Serializable(with = ColorHexSerializer::class)
data class Color(val rgb: Int)
object ColorHexSerializer: KSerializer<Color> {
override fun deserialize(decoder: Decoder): Color =
decoder.decodeStructure(descriptor){
var rgb = 0xFFFFFF
if (decodeSequentially()){
rgb = decodeStringElement(descriptor, 0).substring(2).toInt(16)
}else{
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> rgb = decodeStringElement(descriptor, 0).substring(2).toInt(16)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
}
Color(rgb)
}
override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("SerialExample.Color"){
element<Int>("rgb")
}
override fun serialize(encoder: Encoder, value: Color) {
encoder.encodeStructure(descriptor){
encodeStringElement(descriptor,0, "0x"+value.rgb.toString(16).padStart(6,'0'))
}
}
}
fun main() {
// val stringToColorMapSerializer: KSerializer<Map<String, SerialExample.Color>> = MapSerializer(String.serializer(),SerialExample.Color.serializer())
// println(stringToColorMapSerializer.descriptor)
val red = Color(0xff0000)
println(Json.encodeToString(red))
val red2 = Json.decodeFromString<Color>("""{"rgb":"0xff0000"}""")
println(red.rgb)
println(red2.rgb)
println(red == red2)
//{"rgb":"0xff0000"}
//16711680
//16711680
//true
}
Copy the code
The color values are actually held in Int, but the printed Json string is represented as a hexadecimal string, which is easier to read and can be reverted back to Int when deserialized.