Kotlin:Reified类型参数使Gson失败
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin:Reified类型参数使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
使用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
,并且由于泛型被擦除,因此String
和Serializable
之间的不匹配作为类型参数没有类别转换异常。自从String
实施Serializable
以来,它最终会以任何方式运作。
如果您稍微修改示例,例如通过指定不同类型的List
,发生不同的强制转换,则会遇到麻烦:
val deser = fromJson<LinkedList<Serializable>>(ser)
投掷java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.LinkedList
你需要reified T
能够传递类型信息,但它也意味着失败将更早发生,并且不会因类型擦除而被忽视,就像在非reified
版本中一样。
正如@Jorn Vernee所提到的,序列化/反序列化json需要reified
。如果没有添加reified
,它将被视为List<Object>
,最终反序列化的对象将具有List<LinkedTreeMap>
类型,当您尝试将这些对象用作接口时,您将获得error。
要序列化/反序列化接口,您需要注册自定义适配器以添加已实现类的类型信息。这是一个适配器类,我从这个site引用并对其进行一些更改。
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)
}
}
}
将适配器注册到gson对象,并提供类型信息。
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)
}
示例代码:
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)
*/
以上是关于Kotlin:Reified类型参数使Gson失败的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin泛型 ③ ( 泛型 out 协变 | 泛型 in 逆变 | 泛型 invariant 不变 | 泛型逆变协变代码示例 | 使用 reified 关键字检查泛型参数类型 )