Search code examples
androidjsonkotlingsondata-class

Deserialize nested mutable map from with Gson to Kotlin


I cannot convert JSON to Kotlin data class using Gson due to the MutableMap object. The data class

data class MyAction(
    @Key("action") var action: String = "default",
    @Key("data") var data: MutableMap<String, Any> = mutableMapOf()
)

values in the data map are of several types.I tried with TypeToken and Generics, as in here but didn't work. Examples of received json:

  1. {"action":"playVideo","data":{"media":{"id":15060328,"url":"http://url_to_get_item","name":"item name","shortDescription":"short desc"}

  2. {"action":"setSpeed","data":{"value":1}}

  3. {"action":"getProperty","data":{"value":"position"}}


Solution

  • Is not a good practice to design data in that way, but if you have no control on backend here is an example how to deserialize this

    class MyDeserializer : JsonDeserializer<MyAction>{
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): MyAction {
        val myAction = MyAction()
        val action = json.asJsonObject.get("action")
        val data = json.asJsonObject.get("data")
    
        myAction.action = context.deserialize<String>(action, String::class.java)
    
        val myMap = mutableMapOf<String, Any>()
        data.asJsonObject.keySet().forEach {
            when (it) {
                is String -> { myMap[it] = context.deserialize(data.asJsonObject.get(it), String::class.java) }
                is MyCustomObject1 -> { myMap[it] = context.deserialize(data.asJsonObject.get(it), MyCustomObject1::class.java) }
                is MyCustomObject2 -> { myMap[it] = context.deserialize(data.asJsonObject.get(it), MyCustomObject2::class.java) }
                else -> myMap[it] = context.deserialize(data.asJsonObject.get(it), Any::class.java)
            }
        }
    
        myAction.data = myMap
        return myAction
    }
    

    }

    Dont forget to register your deserializer

    fun getSmartGson() = GsonBuilder().registerTypeAdapter(MyAction::class.java, MyDeserializer())