首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Kotlin:具体化类型参数使Gson失败

Kotlin:具体化类型参数使Gson失败
EN

Stack Overflow用户
提问于 2018-01-01 15:27:24
回答 2查看 2.1K关注 0票数 2

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

请使用以下代码:

代码语言:javascript
运行
复制
val toBeSerialized = listOf("1337")
with(Gson()) {
    val ser = toJson(toBeSerialized)
    val deser = fromJson<List<Serializable>>(ser)
}

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

如果将定义为具体化,则失败:

代码语言:javascript
运行
复制
inline fun <reified T> Gson.fromJson(json: String): T = fromJson<T>(json, object : TypeToken<T>() {}.type)

如果将定义为正常类型参数,则它将工作

代码语言:javascript
运行
复制
fun <T> Gson.fromJson(json: String): T = fromJson<T>(json, object : TypeToken<T>() {}.type)

(请注意,让T具体化在这里是没有意义的,只是想了解它在特殊用例中的影响)

使用reified时的异常如下:

代码语言:javascript
运行
复制
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
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-01-01 16:24:37

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

在非具体化版本中,没有实际的类型信息传递给扩展函数。您可以通过打印从令牌获得的类型来验证这一点:

代码语言:javascript
运行
复制
fun <T> Gson.fromJson(json: String): T {
    val tt = object : TypeToken<T>() {}.type;
    println(tt);
    return fromJson(json, tt);
}

在只打印T (即没有实际类型信息可用)的none具体化版本中,它将打印实际类型(+任何Kotlin声明站点方差修饰符,因此List<String>变为List<? extends String>)。(我不知道为什么Gson默默地忽略了缺少类型信息的错误)。

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

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

代码语言:javascript
运行
复制
val deser = fromJson<LinkedList<Serializable>>(ser)

抛出一个java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.LinkedList

您需要reified T才能传递类型信息,但这也意味着失败会发生得更早,并且不会因为类型擦除而忽略,就像在非reified版本中一样。

票数 5
EN

Stack Overflow用户

发布于 2018-01-01 17:11:32

正如@Jorn所提到的,序列化/反序列化json需要reified。如果不添加reified,它将被视为List<Object>,最终反序列化对象将具有List<LinkedTreeMap>类型,当您尝试使用这些对象作为接口时,您将得到错误

若要序列化/反序列化接口,需要注册自定义适配器以添加已实现类的类型信息。下面是一个适配器类,我引用了这个站点并对它做了一些更改。

代码语言:javascript
运行
复制
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对象,并提供类型信息。

代码语言:javascript
运行
复制
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)
}

示例代码:

代码语言:javascript
运行
复制
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)
*/
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48050862

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档