接口返回的 JSON,再离谱也有办法,谈谈 JSON 容错!

1、序

技术简历的技能树这一项中,JSON 和 GSON 都是常客。可是还有面试候选者将他们的理解停留在最简单的使用上。java

"JSON 是一种具备自描述的、独立于语言的、轻量级文本数据交换格式,常常被用于数据的存储和传输。而 GSON 能够帮咱们快速的将 JSON 数据,在对象之间序列化和反序列化。"android

GSON 的 toJson() 和 fromJson() 这两个方法,是 GSON 最基本的使用方式,它很直观,也没什么好说的。但当被问及 GSON 如何对 JSON 数据容错,如何灵活序列化和反序列化的时候,就有点抓瞎了。面试

JSON 数据容错,最简单的方式是让先后端数据保持一致,就根本不存在容错的问题,可是现实场景中,并不如咱们预期的那般美好。json

举两个简单的例子:User 类中的姓名,有些接口返回的 Key 值是 name,而有些返回的是 username,如何作容错呢?再好比 age 字段返回的是如 "18" 这样的字符串,而 Java 对象将其解析成 Int 类型时,虽然 GSON 有必定的类型容错性,这样解析可以成功,可是若是 age 字段的返回值变成了 "" 呢,如何让其不抛出异常,而且设置为默认值 0?后端

在本文中,咱们就来详细看看,GSON 是如何对数据进行容错解析的。api

2、GSON 的容错

2.1 GSON 的常规使用

GSON 是 Google 官方出的一个 JSON 解析库,比较常规的使用方式就是用toJson() 将 Java 对象序列化成 JSON 数据,或者用  fromJson() 将 JSON 数据反序列化成 Java 对象。性能优化

// 序列化
val user = User()
user.userName = "Android开发架构"
user.age = 18
user.gender = 1
val jsonStr = Gson().toJson(user)
Log.i("cxmydev","json:$jsonStr")
// json:{"age":18,"gender":1,"userName":"Android开发架构"}

// 反序列化
val newUser = Gson().fromJson(jsonStr,User::class.java)
Log.i("cxmydev","userName:${newUser.userName}")
// userName:Android开发架构

GSON 很方便,大部分时候并不须要咱们额外处理什么,拿来即用。惟一须要注意的可能就是泛型擦除,针对泛型的解析,无非就是参数的差别而已。架构

在数据都很规范的状况下,使用 GSON 就只涉及到这两个方法,可是针对一些特殊的场景,就没那么简单了。ide

免费获取安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于android面试的题目汇总能够加入【安卓开发架构】

2.2 GSON 的注解

GSON 提供了注解的方式,来配置最简单的灵活性,这里介绍两个注解 @SerializedName 和 @Expose。性能

@SerializedName 能够用来配置 JSON 字段的名字,最多见的场景来自不一样语言的命名方式不统一,有些使用下划线分割,有些使用驼峰命名法。

仍是拿 User 类来举例,Java 对象中定义的用户名称,使用的是 userName,而返回的 JSON 数据中,用的是 user_name,这样的差别就能够用 @SerializedName 来解决。

class User{
    @SerializedName("user_name")
    var userName :String? = null
    var gender = 0
    var age = 0
}

而在前文中,针对同一个 User 对象中的用户名称,如今不一样的接口返回有差别,分别为:nameuser_nameusername,这种差别也能够用 @SerializedName 来解决。

在 @SerializedName 中,还有一个 alternate 字段,能够对同一个字段配置多个解析名称。

class User{
    @SerializedName(value = "user_name",alternate = arrayOf("name","username"))
    var userName :String? = null
    var gender = 0
    var age = 0
}

再来看看 @Expose,它是用来配置一些例外的字段。

在序列化和反序列化的过程当中,总有一些字段是和本地业务相关的,并不须要从 JSON 中序列化出来,也不须要在传递 JSON 数据的时候,将其序列化。

这样的状况用 @Expose 就很好解决。从字面上理解,将这个字段暴露出去,就是参与序列化和反序列化。而一旦使用 @Expose,那么常规的 new Gson() 的方式已经不适用了,须要 GsonBuilder 配合.excludeFieldsWithoutExposeAnnotation() 方法使用。

@Expose 有两个配置项,分别是 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 = "Android开发架构"
    user.age = 18
    user.gender = 1
    user.genderDesc = "男"

    val gson = GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .create()

    val jsonStr = gson.toJson(user)
    Log.i("cxmydev","json:$jsonStr")
    // json:{"gender":1,"genderDesc":"男","user_name":"承香墨影"}

    val newUser = gson.fromJson(jsonStr,User::class.java)
    Log.i("cxmydev","genderDesc:${newUser.genderDesc}")
    // genderDesc:
}

能够看到上面的例子中,genderDesc 用于说明性别的描述,使用@Expose(serialize = true,deserialize = false) 标记后,这个字段只参与序列化(serialize = true),而不参与反序列化(deserialize = false)。

须要注意的是,一旦开始使用 @Expose 后,全部的字段都须要显式的标记是否“暴露”出来。上例中,age 字段没有 @Expose 注解,因此它在序列化和反序列化的时候,均不会存在。

GSON 中的两个重要的字段注解,就介绍完了,正确的使用他们能够解决 80% 关于 GSON 解析数据的问题,更灵活的使用方式,就不是注解能够解决的了。

2.3 GsonBuilder 灵活解析

就像前面的例子中看到的同样,想要构造一个 Gson 对象,有两种方式,new Gson() 和利用 GsonBuilder 构造。

这两种方式均可以构造一个 Gson 对象,可是在这个 Builder 对象,还提供一些快捷的方法,方便咱们更灵活的解析 JSON。

例如默认状况下,GSON 是不会解析为 null 的字段的,而咱们能够经过.serializeNulls() 方法,来让 GSON 序列化为 null 的字段。

// 序列化
val user = User()
user.age = 18
user.gender = 1

val jsonStr = GsonBuilder().create().toJson(user)
Log.i("cxmydev","json:$jsonStr")
// json:{"age":18,"gender":1}

val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user)
Log.i("cxmydev","json1:$jsonStr1")
// json1:{"age":18,"gender":1,"userName":null}

GsonBuilder 还提供了更多的操做:

  1. .serializeNulls() :序列化为 null 的字段。
  2. .setDateFormat():设置日期格式,例如:setDateFormat("yyyy-MM-dd")
  3. .disableInnerClassSerialization():禁止序列化内部类。
  4. .generateNonExcutableJson():生成不可直接解析的 JSON,会多 )]}' 这 4 个字符。
  5. .disableHtmlEscaping():禁止转移 HTML 标签
  6. .setPrettyPrinting():格式化输出

不管是注解仍是 GsonBuilder 中提供的一些方法,都是 GSON 针对一些特殊场景下,为咱们提供的便捷  API,更复杂一些的场景,就不是它们所能解决的了。

2.4 TypeAdapter

若是前面介绍的规则,都知足不了业务了,不要紧,Gson 还有大招,就是使用 TypeAdapter。

这里讲的 TypeAdapter 是一个泛指,它虽然确实是一个 GSON 库中的抽象类,但在 GSON 的使用中,它又不是一个类。

使用 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。InstanceCreator 的使用场景太少了,就不谈了。

TypeAdapter(抽象类)、JsonSerializer(接口)、JsonDeserializer(接口) 均可以理解成咱们前面说的 TypeAdapter 的泛指,他们具体有什么区别呢?

TypeAdapter 中包含两个主要的方法 write() 和 read() 方法,分别用于接管序列化和反序列化。而有时候,咱们并不须要处理这两种状况,例如咱们只关心 JSON 是如何反序列化成对象的,那就只须要实现 JsonDeserializer 接口的 deserialize() 方法,反之则实现 JsonSerializer 接口的 serialize() 方法,这让咱们的接管更灵活、更可控。

须要注意的是,TypeAdapter 之因此称之为大招,是由于它会致使前面介绍的全部配置都失效。但并非使用了 TypeAdapter 以后,全部的规则都须要咱们本身实现。注意看 registerTypeAdapter() 方法的第一个参数是指定了类型的,它只会针对某个具体的类型进行接管。

举个例子就清楚了,例如前文中提到,当一个 "" 的 JSON 字段,碰上一个 Int 类型的字段时,就会致使解析失败,并抛出异常。

// 序列化
val user = User()
user.age = 18
user.gender = 1

val jsonStr = "{\"gender\":\"\",\"user_name\":\"Android开发架构\"}"

val newUser = GsonBuilder().create().fromJson(jsonStr,User::class.java)
Log.i("cxmydev","gender:${gender}")

在上面的例子中,gender 字段应该是一个 Int 值,而 JSON 字符串中的gender 为 "",这样的代码,跑起来会抛JsonSyntaxException: java.lang.NumberFormatException: empty String 异常。

咱们实现 JsonDeserializer 接口,来接管反序列化的操做。

class IntegerDefault0Adapter : JsonDeserializer<Int> {
    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Int {
        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("cxmydev","gender : ${newUser.gender}")
// gender : 0

TypeAdapter 的使用,到这里就介绍完了,这个大招只要放出来,全部 JSON 解析的问题都再也不是问题。TypeAdapter 的适用场景还不少,能够根据具体的需求具体实现,这里就再也不过多介绍了。

另外再补充几个 TypeAdapter 的细节。

1. registerTypeHierarchyAdapter() 的区别

看看源码,细心的朋友应该发现了,注册 TypeAdapter 的时候,还有registerTypeHierarchyAdapter() 方法,它和 registerTypeAdapter() 方法有什么区别呢?

区别就在于,接管的类型类,是否支持继承。例如前面例子中,咱们只接管了 Int 类型,而数字类型还有其余的例如 Long、Float、Double 等并不会命中到。那假如咱们注册的是这些数字类型的父类 Number 呢?使用 registerTypeAdapter() 也不会被命中,由于类型不匹配。

此时就可使用 registerTypeHierarchyAdapter() 方法来注册,它是支持继承的。

2. TypeAdapterFactory 工厂类的使用

使用 registerXxx() 方法能够链式调用,注册各类 Adapter。

若是嫌麻烦,还可使用 TypeAdapterFacetory 这个 Adapter 工厂,配合registerTypeAdapterFactory() 方法,根据类型来返回不一样的 Adapter。

其实只是换个了实现方式,并无什么太大的区别。

3. @JsonAdapter 注解

@JsonAdapter 和前面介绍的 @SerializedName、@Expose 不一样,不是做用在字段上,而是做用在 Java 类上的。

它指定一个“Adapter” 类,能够是 TypeAdapter、JsonSerializer 和 JsonDeserializer 这三个中的一个。

@JsonAdapter 注解只是一个更灵活的配置方式而已,了解一下便可。

3、小结时刻

GSON 很好用,可是也是创建在使用正确的基础上。我见识过一些丑陋的代码,例如多字段场景下,也在 Java 对象中配套写上多个字段,再增长一个方法用于返回多个字段中不会 null 的字段。又或者为了一个 JSON 数据返回的格式,和后端开发“沟通”一下午规范的问题。

坚持规范固然没有错,可是由于别人的问题致使本身的工做没法继续,就不符合精益思惟了。

不抽象,就没法深刻思考,咱们仍是就今天的内容作一个简单的小结。

  1. GSON 能够提供了 toJson() 和 fromJson() 两个简便的方法序列化和反序列化 JSON 数据。
  2. 经过注解 @SerializedName 能够解决解析字段不一致的问题以及多字段的问题。
  3. 经过注解 @Expose 能够解决字段在序列化和反序列化时,字段排除的问题。
  4. GsonBuilder 提供了一些便捷的 API,方便咱们解析数据,例如
  5. 更灵活的解析,使用 TypeAdapter,能够精准定制序列化和反序列化的全过程。

就总结五条吧,多了也记不住。

免费获取安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于android面试的题目汇总能够加入【安卓开发架构】
相关文章
相关标签/搜索