Search code examples
kotlinjson-deserialization

How to serialize/deserialize json with nested field in kotlin?


I am using Json.decodeFromString<User>("json string") (https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md)

model is like

data class User(val id: String, val name: String, val assets: List<Asset>)
data class Asset(val id: String, val sku: String, val name: String)

but input json is like

{
  "data": {
    "id": "userId",
    "name": "userName",
    "body": {
      "assets": [
        {
          "data": {
            "id": "assetId",
            "sku": "assetSku",
            "name": "assetName"
          }
        }
      ]
    }
  }
}

How can I parse json with serializer? Seems not able to parse with delegate and surrogate serializers easily.


Solution

  • Read about https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#under-the-hood-experimental

    Tried something like this:

    @Serializable(with = AssetSerializer::class)
    data class Asset(val id: String, val sku: String, val name: String)
    
    @Serializable(with = UserSerializer::class)
    data class User(val id: String, val name: String, val assets: List<Asset>)
    
    object AssetSerializer: KSerializer<Asset> {
        override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("Asset") {
            element("data", buildClassSerialDescriptor("data") {
                element("id", String.serializer().descriptor)
                element("sku", String.serializer().descriptor)
                element("name", String.serializer().descriptor)
            })
        }
        override fun serialize(encoder: Encoder, value: Asset) {
            require(encoder is JsonEncoder)
            encoder.encodeJsonElement(buildJsonObject {
                put("data", buildJsonObject {
                    put("id", value.id)
                    put("sku", value.sku)
                    put("name", value.name)
                })
            })
        }
        override fun deserialize(decoder: Decoder): Asset {
            require(decoder is JsonDecoder)
            val root = decoder.decodeJsonElement()
            val element = root.jsonObject["data"]!!
            return Asset(
                id = element.jsonObject["id"]!!.jsonPrimitive.content,
                sku = element.jsonObject["sku"]!!.jsonPrimitive.content,
                name = element.jsonObject["name"]!!.jsonPrimitive.content,
            )
        }
    }
    
    object UserSerializer: KSerializer<User> {
        override val descriptor: SerialDescriptor =
        buildClassSerialDescriptor("User") {
            element("data", buildClassSerialDescriptor("data") {
                element("id", String.serializer().descriptor)
                element("name", String.serializer().descriptor)
                element("body", buildClassSerialDescriptor("body") {
                    element("assets", ListSerializer(Asset.serializer()).descriptor)
                })
            })
        }
        override fun serialize(encoder: Encoder, value: User) {
            require(encoder is JsonEncoder)
            encoder.encodeJsonElement(buildJsonObject {
                put("data", buildJsonObject {
                    put("id", value.id)
                    put("name", value.name)
                    put("body", buildJsonObject {
                        put("assets", JsonArray(value.assets.map { asset ->
                            encoder.json.encodeToJsonElement(asset)
                        }))
                    })
                })
            })
        }
        override fun deserialize(decoder: Decoder): User {
            require(decoder is JsonDecoder)
            val root = decoder.decodeJsonElement()
            val element = root.jsonObject["data"]!!
            val assets = element
                .jsonObject["body"]!!
                .jsonObject["assets"]!!
                .jsonArray
                .map { asset ->
                    decoder.json.decodeFromJsonElement(asset)
                }
    
            return Asset(
                id = element.jsonObject["id"]!!.jsonPrimitive.content,
                name = element.jsonObject["name"]!!.jsonPrimitive.content,
                assets = assets,
            )
        }
    }