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