Is it possible to deserialize a JSON structure so that portions of that structure are collected into a nested child object?
So given this JSON structure
{
"root_field1": "This field will be in root",
"root_field2": "This field will be in root",
"child_field1": "This field will be in a child object",
"child_field2": 123
}
Is it possible to use a JSONTransformSerializer
(or some other way) to deserialize the above json into:
@Serializable
data class Root(
@SerialName("root_field1")
val field1: String,
@SerialName("root_field2")
val field2: String,
val child: Child
)
@Serializable
data class Child(
@SerialName("child_field1")
val field1: String,
@SerialName("child_field2")
val field2: Int
)
I've attempted to use a JsonTransformingSerializer
on the Root
, however that simply causes an exception due to the child
element not being found.
I also attempted to set the child
to @Transient
in hopes that would allow me to circumvent the issue, however the JsonTransformingSerializer
still requires a KSerializer
for its underlying class as an input and so that did not work.
Turns out that this was easily doable by combining both a JsonTransformingSerializer<Root>
and a custom KSerializer<Root>
(which I hadn't thought off before) in the following manner:
object RootTransformingSerializer : JsonTransformingSerializer<Root>(RootSerializer) {
private val childSet: Set<String> = setOf("child_field1", "child_field2")
override fun transformDeserialize(element: JsonElement): JsonElement {
val child: MutableMap<String, JsonElement> = mutableMapOf()
return buildJsonObject() {
if (element !is JsonObject) {
return element
}
element.forEach { entry ->
if (entry.key in childSet) {
child.put(entry.key, entry.value)
} else {
put(entry.key, entry.value)
}
}
put("child", JsonObject(child))
}
}
}
The custom KSerializer then looks like the following
import kotlinx.serialization.descriptors.element
object RootSerializer : KSerializer<Root> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("Root") {
element<String>("root_field1")
element<String>("root_field2")
element<Child>("child")
}
override fun deserialize(decoder: Decoder): Commit {
return decoder.decodeStructure(descriptor) {
val rootField1 = decodeStringElement(descriptor, 0)
val rootField2 = decodeStringElement(descriptor, 1)
val child = decodeSerializableElement(descriptor, 2, Child.serializer())
Root(rootField1, rootField2, child)
}
}