Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson with Kotlin nested class

Tags:

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?

like image 646
nioncode Avatar asked Jun 10 '17 10:06

nioncode


1 Answers

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>.

like image 67
Egor Neliuba Avatar answered Oct 12 '22 14:10

Egor Neliuba