Gson should be a common tool for serializing/deserializing objects, but if we deserialize objects such as List<User> collection objects, due to generic erasers, we have to manually construct a TypeToken object and pass in its Type attribute (type is the type of object we are actually deserializing).
Test data, Gson version used in this article
implementation 'com. Google. Code. Gson: gson: 2.9.0'
Copy the code
data class User(
private val name: String = "".private val age: Int = 0.private val money: Double = 0.0
)
Copy the code
val content =
[
{
"name": "john"."age": 10."money": 10.25
},
{
"name": "tom"."age": 25."money": 90.25
},
{
"name": "lily"."age": 55."money": 10.95}]Copy the code
With the help ofTypeToken
Deserialize collection objects (trouble)
// Construct a TypeToken object
val token = object : TypeToken<List<User>>() {}
/ / the serialization
val fromJson = Gson().fromJson<List<User>>(content, token.type)
Copy the code
Output: [User(name= John, age=10, money=10.25), User(name= Tom, age=25, money=90.25), User(name=lily, age=55, money=10.95)]
As you can see, each time we have to construct a TypeToken class to help us deserialize the object, which is a bit tedious. The most desirable way to write this is to call a function that passes in the deserialized content and the object type, and return the collection object directly
TypeToken
What is the type attribute of
If we know what type stands for, we can pass in a new type. If we know what type stands for, we can pass in a new type
From debug, you can see that the object type of this type is$Gson$Types$ParameterizedTypeImpl
Take a look at this class:
private static final class ParameterizedTypeImpl implements ParameterizedType.Serializable {
private final Type ownerType;
private final Type rawType;
private final Type[] typeArguments;
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {...this.ownerType = ownerType == null ? null : canonicalize(ownerType);
this.rawType = canonicalize(rawType);
this.typeArguments = typeArguments.clone(); . }public Type[] getActualTypeArguments() {
return typeArguments.clone();
}
public Type getRawType(a) {
return rawType;
}
public Type getOwnerType(a) {
returnownerType; }}Copy the code
You can see that this class implements the ParameterizedType interface, which stands for ParameterizedType. What does this interface represent
For exampleParameterizedType
// Create a test class
open class OP<T> {}
val test = object: OP<User, String>() {}
val type: ParameterizedType = test.javaClass.genericSuperclass as ParameterizedType
println(type)
println(type.ownerType)
println(type.rawType)
println(Arrays.toString(type.actualTypeArguments))
Copy the code
Output:
This is a common way to get the concrete type of a generic type. Classes that declare generics can be converted to ParameterizedType, where:
- RawType: represents the original type of the current generic class, that is, OP
- ActualTypeArguments: Represents the concrete types of generics carried by generic classes, namely User and String
- OwnerType: Indicates the type of the owner of this type, used if there is an inner class, otherwise null is returned
Try to useParameterizedType
saidList<User>
type
ParameterizedType can be used to represent List
types. First, we define a generic ParameterizedType adapter class:
class ParameterizedTypeAdapter(private val mRawType: Type, private val typeArgument: Type) :
ParameterizedType {
override fun getActualTypeArguments(a): Array<Type> = arrayOf(typeArgument)
override fun getRawType(a): Type = mRawType
override fun getOwnerType(a): Type? = null
}
Copy the code
ParameterizedTypeImpl(List::class.java, User::class.java) is then used directly to represent List
Further encapsulation with Kotlin characteristics
inline fun <reified T> fromJson(content: String): List<T> =
Gson().fromJson(content, ParameterizedTypeImpl(List::class.java, T::class.java))
Copy the code
We then achieve the desired effect from the beginning: passing in the deserialized content and the object type, we can return the generated collection object directly:
val fromJson = fromJson<User>(content)
println(fromJson)
Copy the code
Output:
Another optimization approach tried (buggy)
We considered using Kotlin’s generic realizations directly to optimize deserialization:
inline fun <reified T> fromJson2(content: String): T {
return Gson().fromJson(content, T::class.java)
}
val fromJson4 = fromJson2<List<User>>(content)
Copy the code
But there was a problem, and the print was as follows:
The age field declaration of User is an Int. After deserialization operation, the floating-point type is printed. After Internet search, it seems that it is related to the internal logic of Gson, and we need to use GsonBuild to rewrite the internal deserialization function.