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 ofTypeTokenDeserialize 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

TypeTokenWhat 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$ParameterizedTypeImplTake 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 useParameterizedTypesaidList<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.