I have a Json input like:
{
"type": "type_1",
"data": {
// ...
}
}
data
field can vary depending on type
.
So, I need a deserializer, that looks on type
(enum) and deserializes data
respectively (for instance, for type_1
value it's Type1
class, for type_2
— Type2
, etc).
I thought about a fully-custom deserializer (extending a KSerializer<T>
), but it looks like an overkill.
What's the best (kotlin) way to do such deserialization?
CodePudding user response:
Kotlin way for polymorphic deserialization is to have a plain JSON (with all data
fields on the same level as type
field):
{
"type": "type_1",
// ...
}
and register all subclasses of abstract superclass with serializers module (this step could be skipped if superclass is a sealed
class).
No need for enums - just mark subclasses declarations with respectful @SerialName("type_1")
annotations if its name in JSON differs from fully-qualified class name.
If original JSON shape is a strict requirement, then you may transform it on the fly to a plain one, reducing the task to the previous one.
@Serializable(with = CommonAbstractSuperClassDeserializer::class)
abstract class CommonAbstractSuperClass
@Serializable
@SerialName("type_1")
data class Type1(val x: Int, val y: Int) : CommonAbstractSuperClass()
@Serializable
@SerialName("type_2")
data class Type2(val a: String, val b: Type1) : CommonAbstractSuperClass()
object CommonAbstractSuperClassDeserializer :
JsonTransformingSerializer<CommonAbstractSuperClass>(PolymorphicSerializer(CommonAbstractSuperClass::class)) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val type = element.jsonObject["type"]!!
val data = element.jsonObject["data"] ?: return element
return JsonObject(data.jsonObject.toMutableMap().also { it["type"] = type })
}
}
fun main() {
val kotlinx = Json {
serializersModule = SerializersModule {
polymorphic(CommonAbstractSuperClass::class) {
subclass(Type1::class)
subclass(Type2::class)
}
}
}
val str1 = "{\"type\":\"type_1\",\"data\":{\"x\":1,\"y\":1}}"
val obj1 = kotlinx.decodeFromString<CommonAbstractSuperClass>(str1)
println(obj1) //Type1(x=1, y=1)
val str2 = "{\"type\":\"type_2\",\"data\":{\"a\":\"1\",\"b\":{\"x\":1,\"y\":1}}}"
val obj2 = kotlinx.decodeFromString<CommonAbstractSuperClass>(str2)
println(obj2) //Type2(a=1, b=Type1(x=1, y=1))
//Works for plain JSON shape as well:
val str0 = "{\"type\":\"type_1\",\"x\":1,\"y\":1}"
val obj0 = kotlinx.decodeFromString<CommonAbstractSuperClass>(str0)
println(obj0) //Type1(x=1, y=1)
}