Search code examples
arraysjsonkotlingson

Data Class Either Object or Array


I have a Kotlin data class that has an arg that can either be an Object or Array. Is there a way to de-serialize a string into this class and not care if not an Array but somehow get it into an array of one?

data class Game(var name:List<NameItem>)
data class NameItem(var title: String, var id: Int)

data can come back as both ways a single object or an array of objects( I have no control over the data as it is 3rd party data.

jsonString = "{"game":{"name":{"title":"GameName","id":22}}}"
jsonString = "{"game":{"name":[{"title":"GameName","id":22},{"title":"GameName2","id":23}]}}"

game: Game? = Gson().fromJson(jsonString  Game::class.java)

Solution

  • my suggestions for solving your task

    my solution if name is object, replace it with arrays

    data class Game(var name:List<NameItem> )
    data class NameItem(var title: String, var id: Int)
    
    
    
    fun main(args: Array<String>) {
        var json = "{\"game\":{\"name\":[{\"title\":\"game 1\",\"id\":1},{\"title\":\"game 2\",\"id\":2}]}}"
        println(useJsonParser(json))    //Game(name=[NameItem(title=game 1, id=1), NameItem(title=game 2, id=2)])
        json = "{\"game\":{\"name\":[{\"title\":\"game 1\",\"id\":1}]}}"
        println(useJsonParser(json))    //Game(name=[NameItem(title=game 1, id=1), NameItem(title=game 2, id=2)])
        json = "{\"game\":{\"name\":{\"title\":\"game 1\",\"id\":1}}}" // not array
        println(useJsonParser(json))    //Game(name=[NameItem(title=game 1, id=1)])
    }
    

    version 1 -- created and registry adapter link @Cililing

    fun useJsonParser(json: String): Game? {
        val gson = GsonBuilder().registerTypeAdapter(Game::class.java, GameDeserializer()).create()
        return gson.fromJson(json, Game::class.java)
    }
    
    class GameDeserializer : JsonDeserializer<Game?> {
        override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Game? {
            val gameJson = json!!.asJsonObject.get("game")
            if (gameJson.isJsonObject) {
                val jsonName = gameJson.asJsonObject["name"]
                val list = if (jsonName.isJsonObject) {
                    arrayOf(Gson().fromJson(jsonName, NameItem::class.java))
                } else {
                    val fromJson = Gson().fromJson(jsonName, Array<NameItem>::class.java)
                    fromJson
                }.toList()
                return Game(list)
            }
            return null
        }
    }
    

    version 2 -- manipulating the response

    fun useJsonParser(json:String):Game?{
        val jsonObject = JsonParser().parse(json).asJsonObject.get("game")
        if(jsonObject.asJsonObject["name"].isJsonObject){
            val jsonName = jsonObject.asJsonObject["name"].asJsonObject
            val array = JsonArray()
            array.add(jsonName)
            jsonObject.asJsonObject.add("name", array) // rewrite origin JSON
        }
    
        return Gson().fromJson(jsonObject, Game::class.java)
    
    }
    

    vesrion 3 -- add adapter TypeToken>()

    fun useJsonParser(json: String): Game? {
        val type = object : TypeToken<MutableList<NameItem>>() {}.type
        val gson = GsonBuilder().registerTypeAdapter(type, NameItemDeserializer()).create()
        return gson.fromJson(JsonParser().parse(json).asJsonObject.get("game"), Game::class.java)
    }
    
    class NameItemDeserializer : JsonDeserializer<List<NameItem>?> {
        override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext?): List<NameItem>? {
            with(json){
                return if(isJsonObject){
                    arrayListOf(Gson().fromJson(this,NameItem::class.java))
                }else{
                    Gson().fromJson(this,Array<NameItem>::class.java).toList()
                }
            }
        }
    }