Data Class Either Object or Array

2019-08-17 00:19发布

问题:

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)

回答1:

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()
            }
        }
    }
}


回答2:

You have to write a custom JsonDeserializer. Both or your class should have the same parent class. Then, write a custom JsonDeserializer for this specific type.

For example:

sealed class GameParent() {
    data class Game(val name: String): GameParent()
    data class GameList(val games: List<Game>): GameParent()
}

class GameDeserializer : JsonDeserializer<GameParent> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): GameParent {
        with(json) {
           if(isJsonObject) {
             // parse as Game
           }

           if(isJsonArray) {
             // parse as GameList
           }
        }
    }
}

Then, in your GsonBuilder you have to register this custom JsonDeserializer: gsonBuilder.registerTypeAdapter(GameParent::class.java, GameDeserializer());

Now, whenever your Gson expect GameParent will use registered deserializer.