Gson序列化的TypeToken写起来太麻烦?优化它

4,566 阅读3分钟

Gson应该是大家常用的序列化/反序列化对象的工具,但是如果我们反序列化的是诸如List<User>集合对象,由于泛型擦除的原因,我们得手动构造一个TypeToken对象并传入其type属性作(type就是我们真正要反序列化生成的对象类型)

本文使用的测试数据、Gson版本

implementation 'com.google.code.gson:gson:2.9.0'
data class User(
    private val name: String = "",
    private val age: Int = 0,
    private val money: Double = 0.0
)
val content =
[
  {
    "name": "john",
    "age": 10,
    "money": 10.25
  },
  {
    "name": "tom",
    "age": 25,
    "money": 90.25
  },
  {
    "name": "lily",
    "age": 55,
    "money": 10.95
  }
]

借助TypeToken反序列化集合对象(麻烦)

//构造一个TypeToken对象
val token = object : TypeToken<List<User>>() {}
//序列化
val fromJson = Gson().fromJson<List<User>>(content, token.type)

输出:
[User(name=john, age=10, money=10.25), User(name=tom, age=25, money=90.25), User(name=lily, age=55, money=10.95)]

可以看到,我们每次都得先构造一个TypeToken类来帮助我们反序列化对象,显得有点繁琐,最希望的写法是调用一个函数,传入反序列化的内容以及对象类型,直接返回集合对象,接下来我们按照这个方向开始探索

TypeToken的type属性是什么

上面创建TypeToken的目的就是拿到type属性,如果我们能知道type代表什么,我们手动构造个type传入不就皆大欢喜省时省力了

impicture_20220218_213805.png 从debug调试里,可以看到这个type的对象类型是$Gson$Types$ParameterizedTypeImpl,看下这个类:

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() {
      return rawType;
  }

  public Type getOwnerType() {
      return ownerType;
  }

}

可以看到这个类是实现了ParameterizedType接口,意为参数化类型,这个接口代表什么呢

举例说明ParameterizedType

//创建一个测试类
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))

输出:

impicture_20220218_224750.png

这是一种常见的获取泛型具体类型的方式,声明泛型的类可以转成ParameterizedType类型,其中:

  • rawType:表示当前泛型类的原始类型,也就是OP
  • actualTypeArguments:表示泛型类所携带的泛型的具体类型,也就是User和String
  • ownerType:表示这个类型所有者的类型,用于存在内部类的情况,否则返回null

尝试使用ParameterizedType表示List<User>类型

经过上面对ParameterizedType,我们就可以很轻松使用ParameterizedType表示List<User>类型: 首先我们定义通用的ParameterizedType适配器类:

class ParameterizedTypeAdapter(private val mRawType: Type, private val typeArgument: Type) :
    ParameterizedType {

    override fun getActualTypeArguments(): Array<Type> = arrayOf(typeArgument)

    override fun getRawType(): Type = mRawType

    override fun getOwnerType(): Type? = null
}

然后就可以直接使用ParameterizedTypeAdapter(List::class.java, User::class.java)来表示List<User>

结合Kotlin特性进一步封装下

inline fun <reified T> fromJson(content: String): List<T> =
    Gson().fromJson(content, ParameterizedTypeAdapter(List::class.java, T::class.java))

然后就实现了我们一开始想要的效果:只要传入反序列化的内容以及对象类型,就能直接返回生成的集合对象

val fromJson = fromJson<User>(content)
println(fromJson)

输出:

impicture_20220218_231500.png

尝试过的另一种优化思路(有BUG)

之前考虑过直接利用kotlin的泛型实化来优化反序列化过程:

inline fun <reified T> fromJson2(content: String): T {
    return Gson().fromJson(content, T::class.java)
}

val fromJson4 = fromJson2<List<User>>(content)

但是有点问题,打印结果如下:

impicture_20220218_231937.png User的age字段声明是个Int类型,经过反序列化操作打印出了浮点类型,经过上网查找,好像和Gson内部的逻辑有关系,需要利用GsonBuild重写内部的一个反序列化函数,看起来太麻烦,就放弃了!!