3

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.

Hrafn
  • 2,867
  • 3
  • 25
  • 44
  • Does this answer your question? [Serialize Kotlin nested classes to flat JSON](https://stackoverflow.com/questions/64556699/serialize-kotlin-nested-classes-to-flat-json) – Михаил Нафталь Apr 05 '21 at 11:25
  • @МихаилНафталь Though the usage of a surrogate is clean and could work for the situation I described, it would not work in my situation as I can not use a private object (nor do i want to add yet another public one) to accomplish what I need. I've posted my solution below. – Hrafn Apr 05 '21 at 13:22

1 Answers1

1

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)
        }
}
Waj0
  • 3
  • 1
  • 4
Hrafn
  • 2,867
  • 3
  • 25
  • 44
  • Lets say we have a large class, does that mean we have to add all element in the descriptor function? And is there a way to make it pluggable? So that on top of the normal descriptor, we add/override with new custom elements. – Blessing Charumbira Jul 17 '23 at 10:09