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 } }