When Gson is used to convert a JSON map to a Kotlin/Java Map, and the JSON values contain arrays as "[]"
, there will be a ClassCastException
error if Array<T>
is declared and then accessed. (Array<T>
is equivalent to Java array T[]
):
app/build.gradle:
dependencies {
...
implementation 'com.google.code.gson:gson:2.8.6'
}
JsonConverter.kt (using this guide):
@Throws(JsonSyntaxException::class)
fun gsonMapTest() {
val jsonInput = """
{"keyOne":[],"keyTwo":["valueA"],"keyThree":[]}
""".trimIndent()
// Guidance: https://www.baeldung.com/gson-json-to-map
val jsonMap1 = Gson().fromJson<LinkedTreeMap<String, Array<String>>>(jsonInput, MutableMap::class.java)
val jsonMap2: Map<*,*> = Gson().fromJson(jsonInput, Map::class.java) // MutableMap can also be used
// These work fine
val firstKeyInMap1: String = jsonMap1.keys.first() // returns String
val firstKeyInMap2 = jsonMap2.keys.first() // returns String
// Errors here: "java.lang.ClassCastException: java.util.ArrayList cannot be cast to [Ljava.lang.String;"
val firstValueInMap1: Array<String> = jsonMap1.values.first()
val firstValueInMap2: Array<String> = jsonMap2.values.first() as Array<String>
}
This error happens at runtime:
java.lang.ClassCastException: java.util.ArrayList cannot be cast to [Ljava.lang.String;
But the Map objects are allowed to be created, without any warning or error. It's only when a Map value is accessed that the error occurs.
From the error message, I initially thought that the call to jsonMap1.values.first()
was trying to cast the empty array to a String. I eventually figured out that the "[L"
in the error message means "array of Objects"
If the declarations in the code sample are changed from Array<String>
to ArrayList<String>
then it all works fine.
So my questions are:
Array<T>
declaration, but cause an error when the item is accessed?ArrayList<String>
should be used instead of Array<String>
?Update: Perhaps this is the same problem?: Java gson, ClassCastException on decoding to Map<String,String[]>
As mentioned by user fluffy in his/her comments, one must use a TypeToken
to deserialize a parameterized Generics type, in addition to the Generics delcaration. However, it does not seem to be needed for ArrayList<String>
:
@Test
@Throws(JsonSyntaxException::class)
fun gsonMapTest() {
val jsonInput = """
{"keyOne":[],"keyTwo":["valueA"],"keyThree":[]}
""".trimIndent()
// Guidance: https://www.baeldung.com/gson-json-to-map
val jsonMap1 = Gson().fromJson<LinkedTreeMap<String, ArrayList<String>>>(jsonInput, MutableMap::class.java)
val listType = object : TypeToken<LinkedTreeMap<String, Array<String>>>() {}.type
val jsonMap2: Map<*,*> = Gson().fromJson(jsonInput, listType) // MutableMap can also be used
// These work fine
val firstKeyInMap1: String = jsonMap1.keys.first() // returns String "keyOne"
val firstKeyInMap2 = jsonMap2.keys.first() // returns String "keyOne"
// No longer errors here
val firstValueInMap1: ArrayList<String> = jsonMap1.values.first() // returns empty list
val firstValueInMap2: Array<String> = jsonMap2.values.first() as Array<String> // returns empty array
}