Better way of converting JSON Array to List<T> given a KClass<T> via Gson

782 Views Asked by At

I'm building a multi-platform JSON library as a prototype to get a feel for how to structure things.

Currently i have this extension function to convert from a JSON Array to a List of Objects.

inline fun <reified T : Any> String.fromJsonList() : List<T> = 
    provider.fromJsonList(this, T::class)

Implementing this interface for Jackson is rather straight forward:

class JvaasJackson : JsonProvider {

    val mapper = ObjectMapper();

    ...

    override fun <T : Any> fromJsonList(input: String, type: KClass<T>): List<T> {
        return mapper.readValue(input, mapper.typeFactory.constructCollectionType(List::class.java, type.java)) ?: listOf()
    }

}

Now i'm trying to do the exact same thing with Gson:

class JvaasGson : JsonProvider {

    val gsonNormal = com.google.gson.Gson()
    val gsonPretty = com.google.gson.GsonBuilder().setPrettyPrinting().create()

    ...

    override fun <T : Any> fromJsonList(input: String, type: KClass<T>): List<T> {

        TODO("fix this")

    }

}

Looking at other answers which are in Java, this seems to be the pattern to follow:

Type listType = new TypeToken<List<MyModel>>(){}.getType();
List<MyModel> myModelList = gson.fromJson(jsonArray, listType);

My Kotlin equivalent fails:

val listType = object : TypeToken<List<T>>() {}.type
val myModelList = gsonNormal.fromJson<List<T>>(input, listType) ?: listOf()

with:

java.lang.ClassCastException: java.util.ArrayList cannot be cast to [Ljava.lang.Object;

This:

val listType = object : TypeToken<ArrayList<T>>() {}.type
val myModelList = gsonNormal.fromJson(input, listType) as ArrayList<T>

and this:

val listType = object : TypeToken<ArrayList<T>>() {}.type
val myModelList = gsonNormal.fromJson<ArrayList<T>>(input, listType)

fails with:

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to io.jvaas.integration.gson.SampleClass

where SampleClass is the type of T

If i remove the types and print the result

    val listType = object : TypeToken<ArrayList<T>>() {}.type
    val list= gsonNormal.fromJson<Any>(input, listType)

    println(list)
    println(list::class)
    println(list::class.java)

i see the correct data:

[{blah1=text, blah2=123.0, blah3=567.0}, {blah1=text, blah2=123.0, blah3=567.0}]

class java.util.ArrayList

class java.util.ArrayList

Printing out the first item of that ArrayList, i see however that it's not of type SampleClass, but rather LinkedTreeMap

println((list as List<T>).first()::class)

class com.google.gson.internal.LinkedTreeMap

Converting each item in that list to String, then converting each individual blob of JSON to objects seems to work, but looks very hacky

override fun <T : Any> fromJsonList(input: String, type: KClass<T>): List<T> {

    val listType = object : TypeToken<ArrayList<*>>() {}.type
    val list= gsonNormal.fromJson<List<Any>>(input, listType) ?: listOf<Any>()

    val results = mutableListOf<T>()
    list.forEach { item ->
        results.add(gsonNormal.fromJson(item.toString(), type.java))
    }

    return results

}

Is there a better way of doing this? Or does Gson always convert more complex JSON data into TreeMaps and LinkedTreeMaps?

0

There are 0 best solutions below