问题描述
我正在创建http json客户端.我将Volley与协程结合使用.我想创建通用的http客户端,以便可以在任何地方使用它.
I am creating http json client. I am using Volley in combination with coroutines. I wanted to create generic http client so I can use it everywhere.
我创建了通用扩展方法来将JSON字符串解析为对象.
I have created generic extension method to parse JSON string into object.
inline fun <reified T>String.jsonToObject(exclusionStrategy: ExclusionStrategy? = null) : T {
val builder = GsonBuilder()
if(exclusionStrategy != null){
builder.setExclusionStrategies(exclusionStrategy)
}
return builder.create().fromJson(this, object: TypeToken<T>() {}.type)
}
问题是,当我调用此方法时,没有得到预期的结果.第一次通话会给出正确的结果.对象已初始化.但是第二次调用(我使用传递给方法的通用参数)以异常"LinkedTreeMap无法转换为令牌"结束.
Problem is that when I call this method I don't get expected result. First call gives proper result. Object is initialized. But second call, where I use generic parameter which is passed to method, ends with exception "LinkedTreeMap can not be cast into Token".
protected inline fun <reified T>sendRequestAsync(endpoint: String, data: Any?, method: Int, token: Token?): Deferred<T> {
return ioScope.async {
suspendCoroutine<T> { continuation ->
val jsonObjectRequest = HttpClient.createJsonObjectRequest(
endpoint,
data?.toJsonString(),
method,
Response.Listener {
//this call is successful and object is initialized
val parsedObject : HttpResponse<Token> = it.toString().jsonToObject()
//this call is not successful and object is not initialized properly
val brokenObject : HttpResponse<T> = it.toString().jsonToObject()
continuation.resume(brokenObject.response)
},
Response.ErrorListener {
continuation.resumeWithException(parseException(it))
},
token)
HttpClient.getInstance(context).addToRequestQueue(jsonObjectRequest)
}
}
}
调用泛型方法.
fun loginAsync(loginData: LoginData): Deferred<Token> {
return sendRequestAsync("/tokens/", loginData, Request.Method.POST, null)
}
这是httpresponse数据类的外观.
This is how httpresponse data class looks.
data class HttpResponse<T> (
val response: T
)
我在这里看到了使用Type :: class.java的解决方法,但是我不喜欢这种方法,我想使用经过修饰的关键字和内联关键字. Kotlin中的reified关键字如何工作?
I saw a workaround here using Type::class.java but I don't like this approach and I would like to use reified and inline keywords.How does the reified keyword in Kotlin work?
更新:根据我的进一步研究发现,这是不正确的. crossinline
不会阻止编译器内联lambda,而只是禁止它们影响函数的控制流.我可能将其与关键字 noinline
混合使用,顾名思义,该关键字实际上禁止内联.
Update: This is, as I've found after some further research, NOT true. crossinline
doesn't stop the compiler from inlining lambdas, it just forbids them to influence the function's control flow. I probably mixed it up with the keyword noinline
, which, as the name implies, actually forbids inlining.
但是,我很确定以下部分.但是,我仍然必须找出为什么Gson无法正确确定和/或反序列化类型.我会在了解更多信息后立即对其进行更新.
However, I'm pretty sure about the following part. However, I still have to find out why Gson is unable to determine and/or to deserialize the type correctly. I'll update this post as soon as I know more.
这使我们进入最后一部分,试图解释您收到的奇怪异常.为此,我们必须看一下Gsons的内部原理.
That brings us to the final part which tries to explain the weird exception you received. For that, we have to take a look at Gsons' internals.
内部,Gson具有负责反射序列化和反序列化的两种主要类型: TypeAdapterFactory
和 TypeAdapter< T>.
Internally, Gson has two main types that are responsible for reflective serialization and deserialization: TypeAdapterFactory
and TypeAdapter<T>.
TypeAdapter< T>
仅适应一种特定类型(为该类型提供(反序列化)逻辑).这意味着 Integer
, Double
, List< String>
和 List< Float>
均由不同的 TypeAdapter< T>
s.
A TypeAdapter<T>
only adapts (= provides the (de-)serialization logic for) one specific type. This means that Integer
, Double
, List<String>
and List<Float>
are all handled by different TypeAdapter<T>
s.
TypeAdapterFactory
负责提供匹配的 TypeAdapter< T>
. TypeAdapter< T>
s和 TypeAdapterFactory
s之间的区别非常有用,因为一个工厂可能会创建所有适配器,例如像 List
这样的集合类型,因为它们的工作方式都相似.
TypeAdapterFactory
s are responsible for, as their names already imply, providing matching TypeAdapter<T>
s. The differentiation between TypeAdapter<T>
s and TypeAdapterFactory
s is extremely useful as one factory might create all adapters for e.g. a collection type like List
as they all work in a similar way.
为了确定所需的适配器类型,Gson希望您在调用应处理通用类型的(反序列化)函数时传递 TypeToken< T>
. TypeToken< T>
使用一个技巧"来访问传递给其type参数的类型信息.
In order to determine what kind of adapter you need, Gson expects you to pass a TypeToken<T>
when calling a (de-)serialization function which should process a generic type. TypeToken<T>
uses a "trick" to access the type information passed to its type parameter.
只要您调用 Gson#fromJson(此对象,TypeToken< T>(){} .type)
,Gson就会遍历所有可用的 TypeAdapterFactory
s,直到它找到一个可以提供适当的 TypeAdapter< T>
的代码.Gson带有各种 TypeAdapterFactory
,包括用于原始数据类型,包装器类型,基本集合类型,日期等的工厂.除此之外,Gson还提供了两个特殊工厂:
As soon as you call Gson#fromJson(this, object: TypeToken<T>() {}.type)
, Gson iterates through all available TypeAdapterFactory
s until it finds one that can provide an appropriate TypeAdapter<T>
. Gson comes with a variety of TypeAdapterFactory
s, including factories for primitive data types, wrapper types, basic collection types, date and many more. Besides that, Gson provides two special factories:
- ReflectiveTypeAdapterFactory顾名思义,该工厂尝试以反射方式访问对象的数据.为了适当地适应每个字段的类型,它为每个字段请求一个匹配的TypeAdapter.这是将为(反序列化HttpRequest)选择的工厂.
- ObjectTypeAdapter.Factory此工厂仅返回ObjectTypeAdapter.下面的代码片段显示了它在对象反序列化(分别在HttpRequest对象中的字段)上的作用:
@Override public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
...
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<String, Object>(); // <-----
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map; // <-----
...
}
这就是为什么您使用 com.google.gson.internal.LinkedTreeMap
来获得 ClassCastException
的原因.
That's why you get a ClassCastException
with a com.google.gson.internal.LinkedTreeMap
.
这篇关于协程内部的通用泛型参数不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!