Kotlin:Reified类型参数使Gson失败

时间:2018-01-01 15:27:25

标签: java kotlin gson

我在使用reified类型的函数中使用Gson反序列化时遇到了一种奇怪的行为。只有当interfaces涉及类型参数时才会发生。

请使用以下代码:

val toBeSerialized = listOf("1337")
with(Gson()) {
    val ser = toJson(toBeSerialized)
    val deser = fromJson<List<Serializable>>(ser)
}

第4行使用自定义扩展功能Gson.fromJson(json: String): T

失败如果T被定义为已知:

inline fun <reified T> Gson.fromJson(json: String): T = fromJson<T>(json, object : TypeToken<T>() {}.type)

如果将定义为普通类型参数,可以

fun <T> Gson.fromJson(json: String): T = fromJson<T>(json, object : TypeToken<T>() {}.type)

(请注意,在此处制作T没有任何意义,只是想了解它在特殊用例中的影响)

使用reified时的例外情况如下:

Exception in thread "main" java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends java.io.Serializable. Registering an InstanceCreator with Gson for this type may fix this problem.
    at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
    at com.google.gson.Gson.fromJson(Gson.java:888)
    at com.google.gson.Gson.fromJson(Gson.java:853)
    at com.google.gson.Gson.fromJson(Gson.java:802)
    at SoTestsKt.main(SoTests.kt:25)
Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: java.io.Serializable
    at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117)
    at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49)
    at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223)
    ... 8 more

2 个答案:

答案 0 :(得分:4)

使用reified T的版本失败,因为它试图将“1337”反序列化为Serializable,这是一个接口,因此无法实例化,默认情况下没有类型可以反序列化为Serializable的适配器(就像List<...>一样)。解决此问题的最简单方法是将List<String>作为类型参数传递。

在未定义的版本中,没有实际的类型信息传递给您的扩展功能。您可以通过打印从令牌获得的类型来验证这一点:

fun <T> Gson.fromJson(json: String): T {
    val tt = object : TypeToken<T>() {}.type;
    println(tt);
    return fromJson(json, tt);
}

在仅打印T的未定义版本中(即没有可用的实际类型信息),但在reified版本中它将打印实际类型(+任何Kotlin声明站点方差修饰符) ,因此List<String>变为List<? extends String>)。 (我不知道为什么Gson会默默地忽略丢失类型信息的错误。)

reified版本的工作原因是巧合。由于Gson的反序列化["1337"]的默认类型是ArrayList<String>,这也是你得到的。它恰好可以分配给List,并且由于泛型被删除,因此StringSerializable之间的不匹配作为类型参数没有类强制转换异常。自String实现Serializable以来,它最终会以任何方式运作。

如果稍微修改示例,例如通过指定其他类型的List发生不同的强制转换,则会遇到麻烦:

val deser = fromJson<LinkedList<Serializable>>(ser)

引发java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.LinkedList

您需要reified T才能传递类型信息,但这也意味着故障会更早发生,并且不会因类型擦除而被忽视,例如非{{1} }版本。

答案 1 :(得分:1)

正如@Jorn Vernee所提到的,序列化/反序列化json需要reified。如果未添加reified,则会将其视为List<Object>,最终反序列化对象的类型为List<LinkedTreeMap>,当您尝试将这些对象用作class InterfaceAdapter<T : Serializable> : JsonSerializer<T>, JsonDeserializer<T> { companion object { private val CLASSNAME = "CLASSNAME" private val DATA = "DATA" } @Throws(JsonParseException::class) override fun deserialize(jsonElement: JsonElement, type: Type, jsonDeserializationContext: JsonDeserializationContext): T { val jsonObject = jsonElement.asJsonObject val prim = jsonObject.get(CLASSNAME) as JsonPrimitive val className = prim.asString val klass = getObjectClass(className) return jsonDeserializationContext.deserialize(jsonObject.get(DATA), klass) } override fun serialize(jsonElement: T, type: Type, jsonSerializationContext: JsonSerializationContext): JsonElement { val jsonObject = JsonObject() jsonObject.addProperty(CLASSNAME, jsonElement.javaClass.name) jsonObject.add(DATA, jsonSerializationContext.serialize(jsonElement)) return jsonObject } private fun getObjectClass(className: String): Class<*> { try { return Class.forName(className) } catch (e: ClassNotFoundException) { throw JsonParseException(e.message) } } } 时,您将获得error你的界面。

要序列化/反序列化接口,您需要注册自定义适配器以添加已实现类的类型信息。这是一个适配器类,我从这里引用site并对其进行一些更改。

inline fun <reified T> Any.toJson(): String {
    val builder = GsonBuilder()
    builder.registerTypeAdapter(Serializable::class.java, InterfaceAdapter<Serializable>())
    val gson = builder.create()
    return gson.toJson(this, object : TypeToken<T>() {}.type)
}

inline fun <reified T> fromJson(json: String): T {
    val builder = GsonBuilder()
    builder.registerTypeAdapter(Serializable::class.java, InterfaceAdapter<Serializable>())
    val gson = builder.create()
    return gson.fromJson<T>(json, object : TypeToken<T>() {}.type)
}

将适配器注册到gson对象,并提供类型信息。

data class User(val name: String) : Serializable

val data = listOf(User("Sam"))
val ser = data.toJson<List<Serializable>>()
val deser = fromJson<List<Serializable>>(ser)

println(ser)
println(deser.get(0)::class)
println(deser.get(0))

/* Output
    [{"CLASSNAME":"User","DATA":{"name":"Sam"}}]
    class User
    User(name=Sam)
*/

示例代码:

a href="http://myPage.com/Mac" target="_blank" id="macDownloadButton" class="downloadbutton w-button">Download Mac version</a>