kotlinx.serialization method for deserializing abstract classes

Posted by jerastraub on Sun, 20 Feb 2022 04:47:08 +0100

The opportunity to write this article is because I encountered such a problem in my work. There is an abstract class XView, and its subclasses have two concrete classes (in fact, there are more). One attribute of XRow is list < XView >?.

Target: deserialize the XRow from the json file

Difficulty: the list < XView > attribute is certainly not a list composed of an honest XView instance, and the abstract class has no instance. This attribute is composed of subclasses and concrete classes of XView. This type is not fixed, so it adds some difficulty to deserialization

Solution: kotlinx serialization https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism

abstract class XView {
}

class XText : XView{
    val data: String?
    constructor(
        data: String?,
    ) {
        this.data = data
    }
}

class XRow : XView {
    var children: List<XView>?
    //Omit some attributes
    constructor(
        children: List<XView>?
    ) {
        this.children = children
    }
}

usage method:

Step 1: import plug-ins and packages https://github.com/Kotlin/kotlinx.serialization#android

Step 2: add @ Serializable tag

@Serializable
abstract class XView {
}

@Serializable
class XText : XView{
    val data: String?
    constructor(
        data: String?,
    ) {
        this.data = data
    }
}

@Serializable
class XRow : XView {
    var children: List<XView>?
    //Omit some attributes
    constructor(
        children: List<XView>?
    ) {
        this.children = children
    }
}

Step 3:

object JsonUtil {
    private val module = SerializersModule {
        polymorphic(baseClass = XView::class) {
            subclass(XText::class)
            subclass(XRow::class)
        }
    }

    private val format: StringFormat = Json {
        coerceInputValues = true
        serializersModule = module
        ignoreUnknownKeys = true
        classDiscriminator = CLASS_DISCRIMINATOR
        encodeDefaults = true
    }

    fun decode(jsonString: String): XView {
        return format.decodeFromString<XView>(jsonString)
    }

    const val CLASS_DISCRIMINATOR = "name"
}

Explain the settings in format:

coerceInputValues: set to true,1 When a value in json is null, but the attribute type in the entity class is not an nullable type; 2. When a value in json is a value, but this attribute in the entity class is an enumeration class, but this enumeration class does not include this value; In both cases, the deserialized property value will be forcibly set to the default value. The default is false. An error will be reported in case of the above two situations.

ignoreUnknownKeys: set to true. When some keys in json have no corresponding attributes in the entity class, they will be ignored. The default is false. An error will be reported in case of the above situation.

encodeDefaults: This is related to serialization. Set whether to serialize the default value

classDiscriminator: this setting is the focus of deserializing abstract classes. It defaults to "type". Take the above code as an example. I set it to "name". When deserializing, I first find the value corresponding to the name in json. If the value value is XRow, the object will be deserialized to XRow type. If the value value is XText, the object will be deserialized to XText type. If the value is another type, an error will be reported. If the json object with name=XRow1 resolves to XRow, it's done with the SerialName tag~

@SerialName("XRow1")
@Serializable
class XRow : XView {
    var children: List<XView>?
    //Omit some attributes
    constructor(
        children: List<XView>?
    ) {
        this.children = children
    }
}