Android开发者解析服务端返回的数据时候,一般会使用Gson/FastJson/moshi等数据解析框架。例如笔者项目中用的就是Gson,但是大多数使用者包括我之前都只是停留在使用的阶段。
Gson的toJson() 和 fromJson() 这两个方法是Gson的最基本的使用方式,当被问及Gson如何对Json数据容错,如何灵活序列化和反序列化的时候,就有点懵。
Json数据容错,最简单的方式是让前后端数据保持一致,就根本不存在容错的问题,但是现实场景并不如我们预期那么好。
举几个例子:User类中的姓名,有的接口返回 name ,有的接口返回 username ,如何容错呢? age 字段返回的是 “18” 这样的字符串,而java对象将其解析成 Int 类型的时候,Gson 有一定的类型容错性,能够解析成功,但是如果 age 字段的返回值变成了 “” 、null呢? 如何让其不抛出异常,并且设置默认值为 0?
接下来就详细看看,Gson是如何对数据做容错解析。
常规的使用,除了 toJson() 将 java 对象序列化成 Json 数据,或者 fromJson() 将 Json 数据反序列化成 java 对象,唯一需要注意的就是泛型擦除(下一篇讲),针对泛型的解析,无非就是参数的差异。
Gson 的注解:
@SerializedName 和 @Expose
@SerializedName可以用来配置 Json 字段的名字,最常见的场景就是不同语言的命名方式不统一,有的使用下划线,有的使用驼峰命名。如 user_name 、userName、或者不规范的 username。那如果多个接口返回的数据结构一致,只有 fieldName 不一致,这样就可以通过 @SerializedName 来做容错。
在 @SerializedName 中,还有一个 alternate 字段,用来对同一个字段配置多个解析名称。
class User{
@SerializedName(value = "user_name", alternate = arrayOf("name","username"))
var userName :String? = null
@SerializedName("user_age")
var age = 0
}
再来看 @Expose ,它是用于指定一个字段是否参与序列化和反序列化, 但是一旦使用@Expose,那么常规的 new Gson() 是不能生效的,需要使用 GsonBuilder 配合 .excludeFeildWithoutExposeAnnotation 方法使用,它有两个配置项:serialize 和 deserialize ,用于指定序列化和反序列化是否包含此字段,默认值都是 true
class User{
@SerializedName(value = "user_name",alternate = arrayOf("name","username"))
@Expose
var userName :String? = null
@Expose
var gender = 0
var age = 0
@Expose(serialize = true,deserialize = false)
var genderDesc = ""
}
fun User.gsonTest(){
// 序列化
val user = User()
user.userName = "xyd"
user.age = 18
user.gender = 1
user.genderDesc = "男"
val gson = GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create()
val jsonStr = gson.toJson(user)
Log.d("xyd","json:$jsonStr")
// json:{"gender":1,"genderDesc":"男","user_name":"xyd"}
val newUser = gson.fromJson(jsonStr,User::class.java)
Log.d("xyd","genderDesc:${newUser.genderDesc}")
// genderDesc:
}
可以看到,genderDesc 只参与了序列化,而未参与反序列化。
注意: 一旦使用了 @Expose 后,所有的字段都必须要显式的标记,否则不参与序列化和反序列化。
Gson 中的许多问题可以通过这两个重要的注解解决,但是更灵活的处理方式就需要进阶了。
GsonBuilder 进阶
前面了解到,想要构造一个 Gson 对象,有两种方式:new Gson() 或 利用 GsonBuilder 构造。
例如,默认情况下, Gson 是不会解析 null 字段的,而我们通过 serializeNulls() 方法,来让 Gson 序列化 null 字段。
val user = User()
user.age = 18
user.gender = 1
// 序列化
val jsonStr = GsonBuilder().create().toJson(user)
Log.d("xyd","json:$jsonStr")
// json:{"age":18,"gender":1}
// 反序列化
val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user)
Log.d("xyd","json1:$jsonStr1")
// json1:{"age":18,"gender":1,"userName":null}
GsonBuild 提供了更多的操作:
.serializeNulls()
序列化 null 字段.setDateFormat()
设置日期格式,如 setDateFormate("yyyy-MM-dd").disableInnerClassSerialization
: 禁止序列化内部类.generateNonExcutableJson()
:生成不可直接解析的 JSON,会多)]}'
这 4 个字符。.disableHtmlEscaping()
:禁止转移 HTML 标签.setPrettyPrinting()
:格式化输出
无论是注解还是 GsonBuilder 中提供的一些方法,都是 Gson 针对一些特殊场景为我们提供的便捷 API,更复杂的就不能解决了。
TypeAdapter
如果前面介绍的规则都满足不了业务,Gson 还有更进一步的处理方式,那就是使用 TypeAdapter。这个 TypeAdapter 实际是 Object 类型,也就是一个泛指。
使用 TypeAdapter 就需要用到 GsonBuilder 中的 registerTypeAdapter() 方法。
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof InstanceCreator<?>
|| typeAdapter instanceof TypeAdapter<?>);
if (typeAdapter instanceof InstanceCreator<?>) {
instanceCreators.put(type, (InstanceCreator) typeAdapter);
}
if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
TypeToken<?> typeToken = TypeToken.get(type);
factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter<?>) {
factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
}
return this;
}
可以看到注册方法,需要指定一个数据类型,且除了支持 TypeAdapter ,还支持 JsonSerializer JsonDeserializer。他们的区别是什么?
TypeAdapter(抽象类)、JsonSerializer(接口)、JsonDeserializer(接口)。
TypeAdapter 中包含两个主要的方法 write() 和 read() 方法,分别用于接管序列化和反序列化。
JsonSerializer 只用来接管序列化。
JsonDeserializer 只用来接管反序列化。
举个使用实例:
val user = User()
user.age = 18
user.gender = 1
// 反序列化
val jsonStr = """ {"name":"xyd","age":18,"gender":""} """
val newUser = GsonBuilder().create.fromJson(jsonStr, User:class.java)
Log.e("xyd", "gender")
上面的例子中,gender
应该是一个 Int 值,而 Json 字符串中是 "" , 这样的代码跑起来就会直接报错。怎么处理呢?我们通过实现 JsonDeserializer 接口,来接管反序列化的操作。
class IntegerDefaultAdapter : JsonDeserializer<Int>{
override fun deserialize(json : JsonElement?, typeOfT : Type?, context : JsonDeserializationContext?){
try{
return json!!.getAsInt()
}catch(e : NumberFormatException){
return 0
}
}
}
这样当转换 Int 出现异常时,返回默认值 0。然后使用 registerTypeAdapter() 方法加入其中。
val newUser = GsonBuilder()
.registerTypeAdapter(Int::class.java, IntegerDefault0Adapter())
.create().fromJson(jsonStr,User::class.java)
Log.i("xyd","gender : ${newUser.gender}")
// gender : 0
TypeAdapter 的使用,到这里就介绍完了。