Gson-解析-Json-容错才是关键,举几个常用的实例,图文详解

                          context: JsonDeserializationContext?): Int {
    if (json?.getAsString().equals("")) {
        return 0
    }
    try {
        return json!!.getAsInt()
    } catch (e: NumberFormatException) {
        return 0
    }
}

}

fun intDefault0(){
val jsonStr = “”"
{
“name”:“萧晓”,
“age”:""
}
“”".trimIndent()
val user = GsonBuilder()
.registerTypeAdapter(
Int::class.java,
IntDefaut0Adapter())
.create()
.fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“user: ${user.toString()}”)
}


在 IntDefaut0Adapter 中,首先判断数据字符串是否为空字符串 `""`,如果是则直接返回 0,否则将其按 Int 类型解析。在这个例子中,将整型 0 作为一个异常参数进行处理。

### 2.3 null、[]、List 转 List

还有一些小伙伴比较关心的,对于 JSONObject 和 JSONArray 兼容的问题。

例如需要返回一个 List,翻译成 JSON 数据就应该是方括号 `[]` 包裹的 JSONArray。但是在列表为空的时候,服务端返回的数据,什么情况都有可能。

{
“name”:“萧晓”,
“languages”:[“EN”,“CN”] // 理想的数据
// “languages”:""
// “languages”:null
// “languages”:{}
}


例子的 JSON 中,`languages` 字段表示当前用户所掌握的语言。当语言字段没有被设置的时候,服务端返回的数据不一致,如何兼容呢?

我们在原本的 User 类中,增加一个 languages 的字段,类型为 ArrayList。

var languages = ArrayList()


在 Java 中,列表集合都会实现 List 接口,所以我们在实现 JsonDeserializer 的时候,解析拦截的应该是 List。

在这个情况下,可以使用 JsonElement 的 `isJsonArray()` 方法,判断当前是否是一个合法的 JSONArray 的数组,一旦不正确,就直接返回一个空的集合即可。

class ArraySecurityAdapter:JsonDeserializer<List<>>{
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): List<
> {

    if(json.isJsonArray()){
        val newGson = Gson()
        return newGson.fromJson(json, typeOfT)
    }else{
        return Collections.EMPTY_LIST
    }
}

}

fun listDefaultEmpty(){
val jsonStr = “”"
{
“name”:“萧晓”,
“age”:“18”,
“languages”:{}
}
“”".trimIndent()
val user = GsonBuilder()
.registerTypeHierarchyAdapter(
List::class.java,
ArraySecurityAdapter())
.create()
.fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“user: ${user.toString()}”)
}


其核心就是 `isJsonArray()` 方法,判断当前是否是一个 JSONArray,如果是,再具体解析即可。到这一步就很灵活了,你可以直接用 Gson 将数据反序列化成一个 List,也可以将通过一个 for 循环将其中的每一项单独反序列化。

需要注意的是,如果依然想用 Gson 来解析,需要重新创建一个新的 Gson 对象,不可以直接复用 JsonDeserializationContext,否则会造成递归调用。

另外还有一个细节,在这个例子中,调用的是 `registerTypeHierarchyAdapter()` 方法来注册 TypeAdapter,它和我们前面介绍的 `registerTypeAdapter()` 有什么区别呢?

通常我们会根据不同的场景,选择不同数据结构实现的集合类,例如 ArrayList 或者 LinkedList。但是 `registerTypeAdapter()` 方法,要求我们传递一个明确的类型,也就是说它不支持继承,而 `registerTypeHierarchyAdapter()` 则可以支持继承。

我们想用 List 来替代所有的 List 子类,就需要使用 `registerTypeHierarchyAdapter()` 方法,或者我们的 Java Bean 中,只使用 List。这两种情况都是可以的。

![](https://upload-images.jianshu.io/upload_images/15233854-c2b2ba8581cf2633.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#### 2.4 保留原 Json 字符串

看到这个小标题,可能会有疑问,保留原 Json 字符串是一个什么情况?得到的 Json 数据,本身就是一个字符串,且挺我细细说来。

举个例子,前面定义的 User 类,需要存到 SQLite 数据库中,语言(languages)字段也是需要存储的。说到 SQLite,当然优先使用一些开源的 ORM 框架了,而不少优秀的 ORM-SQLite 框架,都通过外键的形式支持了一对多的存储。例如一篇文章对应多条评论,一条用户信息对应对应多条语言信息。

这种场景下我们当然可以使用 ORM 框架本身提供的一对多的存储形式。但是如果像现在的例子中,只是简单的存储一些有限的数据,例如用户会的语言(languages),这种简单的有限数据,用外键有一些偏重了。

此时我们就想,要是可以直接在 SQLite 中存储 languages 字段的 JSON,将其当成一个字符串去存储,是不是就简单了?把一个多级的结构拉平成一级,剩下的只需要扩展出一个反序列化的方法,对业务来说,这些操作都是透明的。

那拍脑袋想,如果 Gson 有简单的容错,那我们将这个解析的字段类型定义成 String,是不是就可以做到了?

@SerializedName(“languages”)
var languageStr = “”


很遗憾,这并没有办法做到,如果你这样使用,你将得到一个 IllegalStateException 的异常。

Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY at line 4 column 18 path $.languages


之所以会出现这样的情况,简单来说,虽然 `deserialize()` 方法传递的参数都是 JsonElement,但是 JsonElement 只是一个抽象类,最终会根据数据的情况,转换成它的几个实现类的其中之一,这些实现类都是 final class,分别是 JsonObject、JsonArray、JsonPrimitive、JsonNull,这些从命名上就很好理解了,它们代表了不通的 JSON 数据场景,就不过多介绍了。

使用了 Gson 之后,遇到花括号 `{}` 会生成一个 JsonObject,而字符串则是基本类型的 JsonPrimitive 对象,它们在 Gson 内部的解析流程是不一样的,这就造成了 IllegalStateException 异常。

**那么接下来看看如何解决这个问题。**

既然 TypeAdapter 是 Gson 解析的银弹,找不到解决方案,用它就对了。思路继续是用 JsonDeserializer 来接管解析,这一次将 User 类的整个解析都接管了。

class UserGsonAdapter:JsonDeserializer{
override fun deserialize(json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?): User {

    var user = User()
    if(json.isJsonObject){
        val jsonObject = JSONObject(json.asJsonObject.toString())
        user.name = jsonObject.optString("name")
        user.age = jsonObject.optInt("age")
        user.languageStr = jsonObject.optString("languages")
        user.languages = ArrayList()
        val languageJsonArray = JSONArray(user.languageStr)
        for(i in 0 until languageJsonArray.length()){
            user.languages.add(languageJsonArray.optString(i))
        }
    }
    return user
}

}

fun userGsonStr(){
val jsonStr = “”"
{
“name”:“萧晓”,
“age”:“18”,
“languages”:[“CN”,“EN”]
}
“”".trimIndent()
val user = GsonBuilder()
.registerTypeAdapter(
User::class.java,
UserGsonAdapter())
.create()
.fromJson(jsonStr,User::class.java)
Log.i(“cxmydev”,“user: \n${user.toString()}”)
}


在这里我直接使用标准 API org.json 包中的类去解析 JSON 数据,当然你也可以通过 Gson 本身提供的一些方法去解析,这里只是提供一个思路而已。

最终 Log 输出的效果如下:

{
“name”:“萧晓”,
“age”:18,
“languagesJson”:[“CN”,“EN”],
"languages size:"2
}


在这个例子中,最终解析还是使用了标准的 JSONObject 和 JSONArray 类,和 Gson 没有任何关系,Gson 只是起到了一个桥接的作用,好像这个例子也没什么实际用处。

不谈场景说应用都是耍流氓,那么如果是使用 Retrofit 呢?Retrofit 可以配置 Gson 做为数据的转换器,在其内部就完成了反序列化的过程。这种情况,配合 Gson 的 TypeAdapter,就不需要我们在额外的编写解析的代码了,网络请求走一套逻辑即可。

如果觉得在构造 Retrofit 的时候,为 Gson 添加 TypeAdapter 有些入侵严重了,可以配合 `@JsonAdapter` 注解使用。

## 三. 小结时刻

针对服务端返回数据的容错处理,很大一部分其实都是来自双端没有保证数据一致的问题。而针对开发者来说,要做到外部数据均不可信的,客户端不信本地读取的数据、不信服务端返回的数据,服务端也不能相信客户端传递的数据。这就是所谓防御式编程。

**言归正传,我们小结一下本文的内容:**

1.  TypeAdapter(包含JsonSerializer、JsonDeserializer) 是 Gson 解析的银弹,所有 Json 解析的定制化要求都可以通过它来实现。
2.  `registerTypeAdapter()` 方法需要制定确定的数据类型,如果想支持继承,需要使用 `registerTypeHierarchyAdapter()` 方法。
3.  如果数据量不大,推荐使用 JsonSerializer 和 JsonDeserializer。
4.  针对整个 Java Bean 的解析接管,可以使用 `@JsonAdapter` 注解。

### 最后

### 最后

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,**从来都是我们去适应环境,而不是环境来适应我们!**

最后,我再重复一次,**如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究**。

**对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助**。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

为了大家能够顺利进阶中高级、架构师,我特地为大家准备了**一套高手学习的源码和框架视频等精品Android架构师教程**,保证你学了以后保证薪资上升一个台阶。

**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**

以下是今天给大家分享的一些独家干货:

![](https://img-blog.csdnimg.cn/img_convert/459e6e60de0a75728a8164cda74060e7.png)

和框架视频等精品Android架构师教程**,保证你学了以后保证薪资上升一个台阶。

**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**

以下是今天给大家分享的一些独家干货:

[外链图片转存中...(img-cZ1heZpm-1630667420625)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值