关于Android数据解析的容错,你做过这些么?

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 的使用,到这里就介绍完了。

原文地址:https://www.cnblogs.com/brin/p/11468297.html