I fail to properly deserialize a nested Kotlin class as the correct type with Gson. When I try to deserialize the same Java class, it works fine.
Java Class:
package example;
import java.util.List;
import java.util.Map;
class TestJsonJava {
Map<String, List<Entry>> outer;
static class Entry {
String inner;
}
}
Kotlin class:
package example
class TestJsonKotlin {
var outer: Map<String, List<Entry>>? = null
class Entry {
var inner: String? = null
}
}
Kotlin main:
package example
import com.google.gson.GsonBuilder
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val json = """
{
"outer": {
"keyA": [
{
"inner": "hello"
}
]
}
}
"""
val javaObject = GsonBuilder().create().fromJson(json, TestJsonJava::class.java)
val javaWorks = javaObject.outer!!["keyA"]!![0] is TestJsonJava.Entry
println("Java works : $javaWorks")
println(javaObject.outer!!["keyA"]!![0].inner)
val kotlinObject = GsonBuilder().create().fromJson(json, TestJsonKotlin::class.java)
val kotlinWorks = kotlinObject.outer!!["keyA"]!![0] is TestJsonKotlin.Entry
println("Kotlin works: $kotlinWorks")
println(kotlinObject.outer!!["keyA"]!![0].inner)
}
}
}
This prints:
Java works : true
hello
Kotlin works: false
Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to example.TestJsonKotlin$Entry
at example.Main$Companion.main(TestJsonmain.kt:28)
at example.Main.main(TestJsonmain.kt)
How do I tell gson to deserialize the value of keyA
to a List<Entry>
instead of LinkedTreeMap
?
Annotating the List
with @JvmSuppressWildcards
seems to help:
var outer: Map<String, @JvmSuppressWildcards List<Entry>>? = null
If we don't use @JvmSuppressWildcards
, then the Kotlin code is translated to:
Map<String, ? extends List<TestJsonKotlin.Entry>> outer;
If we do use it, then the code is translated to:
Map<String, List<TestJsonKotlin.Entry>> outer;
The difference is in the wildcard ? extends
– this is not supported even if you write this in Java. I have filed an issue in Gson's repository here.
The javap
highlights the difference between the two cases:
// `TestJsonKotlin` with `val outer` is compiled to
public final class TestJsonKotlin {
private final java.util.Map<java.lang.String, java.util.List<TestJsonKotlin$Entry>> outer;
public final java.util.Map<java.lang.String, java.util.List<TestJsonKotlin$Entry>> getOuter();
// ...
}
// `TestJsonKotlin` with `var outer` is compiled to
public final class TestJsonKotlin {
private java.util.Map<java.lang.String, ? extends java.util.List<TestJsonKotlin$Entry>> outer;
public final java.util.Map<java.lang.String, java.util.List<TestJsonKotlin$Entry>> getOuter();
// ...
}
The var
case adds ? extends
in front of the java.util.List<TestJsonKotlin$Entry>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With