全球聚焦:[Gson]Gson 解析 Json 容错策略
腾讯云 2023-03-28 18:12:50

一. 序章

文章评论里后台有一些小伙伴,针对具体数据容错的场景,提出了具体的问题。今天就在这篇文章里统一解答,并且给出解决方案。


(资料图)

二. GSON 数据容错实例

就像前文中介绍的一样,GSON 已经提供了一些简单的注解,去做数据的容错处理。更复杂的操作,就需要用到 TypeAdapter 了,需要注意的是,一旦上了 TypeAdapter 之后,注解的配置就会失效。

2.1 什么是 TypeAdapter

TypeAdapter 是 GSON 2.1 版本开始支持的一个抽象类,用于接管某些类型的序列化和反序列化。TypeAdapter 最重要的两个方法就是 write()read(),它们分别接管了序列化和反序列化的具体过程。

如果想单独接管序列化或反序列化的某一个过程,可以使用 JsonSerializer 和 JsonDeserializer 这两个接口,它们组合起来的效果和 TypeAdapter 类似,但是其内部实现是不同的。

简单来说,TypeAdapter 是支持流的,所以它比较省内存,但是使用起来有些不方便。而 JsonSerializer 和 JsonDeserializer 是将数据都读到内存中再进行操作,会比 TypeAdapter 更费内存,但是 API 使用起来更清晰一些。

虽然 TypeAdapter 更省内存,但是通常我们业务接口所使用的那点数据量,所占用的内存其实影响不大,可以忽略不计。

因为 TypeAdapter、JsonSerializer 以及 JsonDeserializer 都需要配合 GsonBuilder.registerTypeAdapter()方法,所以在本文中,此种接管方式,统称为 TypeAdapter 接管。

2.2 空字符串转 0

对于一些强转有效的类型转换,GSON 本身是有一些默认的容错机制的。比如:将字符串 “18” 转换成 Java 中整型的 18,这是被默认支持的。

例如我有一个记录用户信息的 User 类。

class User{    var name = ""    var age = 0    override fun toString(): String {        return """            {                "name":"${name}",                "age":${age}            }        """.trimIndent()    }}

User 类中包含 nameage两个字段,其中 age对应的 JSON 类型,可以是 18也可以是 "18",这都是允许的。

{"name":"承香墨影","age":18 // "age":"18"}

那假如服务端说,这个用户没有填年龄的信息,所以直接返回了一个空串 "",那这个时候客户端用 Gson 解析就悲剧了。

这当然是服务端的问题,如果数据明确为 Int 类型,那么就算是默认值也应该是 0 或者 -1。

但遇到这样的情况,你还用默认的 GSON 策略去解析,你将得到一个 Crash。

Caused by: com.google.gson.JsonSyntaxException: - java.lang.NumberFormatException: --empty String

没有一点意外也没有一点惊喜的 Crash 了,那接下来看看如何解决这样的数据容错问题?

因为这里的场景中,只需要反序列化的操作,所以我们实现 JsonDeserializer 接口即可,接管的是 Int 类型。直接上例子吧。

class IntDefaut0Adapter : JsonDeserializer {    override fun deserialize(json: JsonElement?,                              typeOfT: Type?,                              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>{    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。这两种情况都是可以的。

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注解使用。

三. 小结时刻

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

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

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

全球聚焦:[Gson]Gson 解析 Json 容错策略

2023-03-28 18:12:50

6名游客被困雪山,阿坝消防提醒出行需注意当地天气-环球观天下

2023-03-28 17:25:07

每日报道:社保卡能取钱吗?满足特殊情况才可以取

2023-03-28 16:34:12

百亿补贴、大额满减、京东秒杀,武极电脑强性能高性价比主机推荐

2023-03-28 16:02:42

当前热讯:美国银行业危机给商业地产埋下“大雷”!写字楼地产或成最脆弱一环

2023-03-28 14:22:08

培养青少年足球人才,多级俱乐部梯队赛事来了

2023-03-28 13:07:46

最资讯丨金溪镇扎实开展自建房屋安全隐患大“体检”

2023-03-28 10:43:13

博云新材:3月27日获融资买入388.94万元

2023-03-28 09:20:33

速派/柯迪亚克等 斯柯达2023新车规划曝光

2023-03-28 08:13:05

苹果税伤不起!抖音下架课程虚拟商品

2023-03-28 05:51:03

世界观点:七龙珠第一部普通话版(七龙珠第一部国语版)

2023-03-28 01:09:10

2023年3月27日山东省四甲基乙二胺价格最新行情预测 今日热闻

2023-03-27 22:21:11

讯息:交建股份: 安徽省交通建设股份有限公司2023年第一次临时股东大会决议公告

2023-03-27 20:29:07

今日热门!《Thunderbolt Fantasy 东离剑游纪》第四季確定開拍

2023-03-27 19:09:44

渝北洛碛:劝阻农耕用火 巡护辖区铁路 当前播报

2023-03-27 17:50:20

逃回国男子亲述缅北恐怖遭遇

2023-03-27 16:40:36

天津集中科技、人才和创新优势 打造“东疆之星”高成长基地

2023-03-27 15:08:17

Temu狂飙,卖家分化:有人利润高过亚马逊,有人一单赚不到一块钱

2023-03-27 14:10:39

沈丘农商银行:开展读书复盘提升训练 观焦点

2023-03-27 12:27:15

杭州:全市新增筹建保租房1.84万套 目标全年新增7万套 环球快资讯

2023-03-27 11:21:28

深圳这些人,公示

2023-03-27 10:14:26

全球快资讯丨记者调查:非学科类培训机构普遍不顾限令推销大课包

2023-03-27 08:58:40

招商蛇口上海公司再启新章 打造上海城市更新样本|天天要闻

2023-03-27 07:04:53

柠汁腊羊肉沙拉_关于柠汁腊羊肉沙拉简述 天天速读

2023-03-27 02:02:05

快报:退休钟局长孙女私生活曝光!穿和服与男友开房,留学澳洲高调炫富

2023-03-26 22:16:40

中国钢铁业进入深度调整期 转型发展如何破局?-全球通讯

2023-03-26 19:54:46

魔切怎么出_魔切效果

2023-03-26 17:49:33

百度网络盘怎么下载_百度网盘下载网络异常

2023-03-26 15:34:24

怎么将hao123设为ie浏览器的主页 最新

2023-03-26 13:05:05

吃汤圆的含义是什么

2023-03-26 11:05:25

疯狂体育联合举办国际职业拳击赛,5月1日澳门上演揭幕战|当前热讯

2023-03-26 10:42:41

厦门智慧公交技术创新居全国城市前列 示范工程通过交通运输部验收 世界聚看点

2023-03-26 08:01:12

学信网上查不到双学位_双学位学信网查不到|焦点快报

2023-03-26 04:35:34

全球热议:石灰吟作者于谦资料50字_石灰吟作者于谦的资料 100字左右

2023-03-25 22:55:24

手提电脑内置麦克风为什么没声音-环球关注

2023-03-25 21:31:44

洗面奶哪个牌子的好用?让你洗脸不再刺痛,这四款洗面奶值得一试-每日头条

2023-03-25 20:15:36

五指山避暑山庄绿化率怎么样?五指山避暑山庄位置在哪?

2023-03-25 17:20:09

诈骗罪被判刑后钱还要的回来吗

2023-03-25 14:58:10

如何恢复清空回收站

2023-03-25 14:29:29

半夏投资李蓓:股票市场震荡重心上移,后续2年房价会上涨 新消息

2023-03-25 12:59:14

全球聚焦:游泳一小时消耗多少卡路里

2023-03-25 10:48:27

滚动:2010年winds天天向上视频_winds天天向上

2023-03-25 09:00:59

世界聚焦:锡纸烫男士操作方法

2023-03-25 06:41:47

切开了的南瓜放时间长了能吃吗?南瓜什么情况不能吃 全球速看

2023-03-25 06:06:05

语重心长的近义词是什么标准答案(语重心长的近义词是什么)_短讯

2023-03-25 01:02:20

凯立新材:2022年净利约2.21亿元 同比增加36.02%

2023-03-24 22:07:42

为传承和发展注入新动力 带你了解“新锐渝商”“青年渝商”这些亮点 世界新动态

2023-03-24 20:38:54

年报揭秘管理费和调仓动向,百亿基金经理们都怎么操作?-天天热头条

2023-03-24 19:03:23

哪些飞机有wifi

2023-03-24 17:35:31

入列!南航汕头公司迎来首架“阿娇”! 环球速看料

2023-03-24 17:27:45

2023北京科技大学天津学院材料与环境学院招聘启事

2023-03-24 16:08:51

头条:榆社县教育局(关于榆社县教育局介绍)

2023-03-24 15:09:03

建筑工程合同担保保证协议书如何写|天天日报

2023-03-24 13:14:53

“特朗普被捕”AI图片,为何能堪比大片 环球观速讯

2023-03-24 11:59:11

无锡→香港,可以坐高铁啦!

2023-03-24 10:52:23

全球新消息丨科比在NBA有没打架

2023-03-24 10:18:17

英雄互娱(002389)

2023-03-24 09:56:28

火锅圈进入猎杀时刻_环球今亮点

2023-03-24 08:56:28

23岁&18岁!琼阿梅尼与加维均9000万欧,突破1亿欧是否指日可待?

2023-03-24 06:29:57

今亮点!【名医问诊】孕产妇有感冒症状怎么办?该吃什么药

2023-03-24 02:02:30

今日观点!乡村爱情第3部全集视频_乡村爱情 第3部

2023-03-23 22:59:38

【全球报资讯】LOL寡妇怎么打野?

2023-03-23 21:12:23

工信部召开重点行业协会座谈会 分析一季度工业经济运行形势

2023-03-23 20:55:47

环球消息!东亚前海证券:给予永泰能源增持评级

2023-03-23 19:01:41

主力复盘:46亿涌入半导体 超12亿封板科大讯飞|天天微资讯

2023-03-23 17:40:41

天天观速讯丨出口火爆,新能源车充电桩海外需求暴涨218%

2023-03-23 16:26:49

湖北133人因这个问题被追责问责_最新

2023-03-23 15:11:28

【世界速看料】重磅政策鼓励油气与新能源协同开发,“三桶油”竞逐新能源获力挺

2023-03-23 14:23:16

天天快看:鸡属于什么动物

2023-03-23 13:30:42

每日消息!郑州直达香港高铁4月1日出发,首班车票已售罄

2023-03-23 12:30:59

工银安盛人寿湖北分公司开展学雷锋活动 世界最资讯

2023-03-23 11:10:57

西瓜的英语怎么写_西瓜的季节 全球信息

2023-03-23 09:46:17

新建区气象台发布雷电黄色预警信号【III级/较重】_即时

2023-03-23 08:13:26

三角洲特种部队6攻略

2023-03-23 06:04:48

全球新动态:共和:用好“引育留”三要素 激发人才新活力

2023-03-23 05:14:29

爱柯迪:新能源客户项目订单持续放量,2022年年归母净利润同比增109.29%,拟10派2.3元

2023-03-23 00:54:52

腾讯高管谈“生成式AI”:有可能纳入微信和QQ_全球速看

2023-03-22 21:28:59

环球热头条丨河北定州:打好种植“特色牌”助力乡村振兴

2023-03-22 19:23:06

魔兽地图简单的防守攻略,魔兽防守地图怎么做啊最好详细一点

2023-03-22 18:41:26

最资讯丨包贝尔退出辣庄火锅

2023-03-22 17:55:36

王慧文 AI 创业公司光年之外启动 A 轮融资:诚邀产品经理共创 AGI 时代

2023-03-22 16:05:29

世界看点:2023杭州小客车久摇不中指标能转移给配偶吗

2023-03-22 15:14:27

快看!包银高铁最新进展|全球热文

2023-03-22 13:02:09

男子将200万房子80元“卖”了 如此操作为哪般?

2023-03-22 11:01:34

当前关注:瑞典养老基金Alecta全数出售第一信托银行股份,亏损75亿瑞典克朗

2023-03-22 09:00:58

造梦西游3太上老君在哪里打 造梦西游3太上老君在

2023-03-22 06:11:56

按摩椅结构图_按摩椅结构_当前热门

2023-03-22 01:41:18

汽车零部件:由于资金问题福泰尔轮胎部分设备已抵押 全球快资讯

2023-03-21 22:06:40

央媒看莲湖|招聘大厅的求职者去哪儿了?|当前速读

2023-03-21 19:02:37

汽车美容店多少投资(汽车美容店投资多少钱)

2023-03-21 17:10:50

焦点报道:慰安妇是什么

2023-03-21 14:57:11

徐沛快速通道电话_徐沛快速通道

2023-03-21 12:56:54

全球观天下!燕京啤酒董秘回复:燕京啤酒积极拥抱消费潮流,产品和营销精准对位年轻消费群体

2023-03-21 10:53:59

一把油纸伞撑起非遗传承之美02

2023-03-21 09:04:52

全球热推荐:美国得州达拉斯一所高中发生枪击事件 致1死1伤

2023-03-21 06:40:29

3月20日基金净值:汇添富价值创造定开混合最新净值1.6365,跌1.43%-当前热点

2023-03-21 02:04:20

世界今日讯!2023杭州二孩家庭直接申领小客车指标(认定条件+申领指南)

2023-03-20 21:54:19

【快播报】长春高新:拟向控股股东出售高新地产100%股权

2023-03-20 19:15:53

小雕_xiaodiao

2023-03-20 17:14:26

焦点信息:03月20日12时四川宜宾疫情数据 阳了以后为什么会腰疼?应该怎么办?

2023-03-20 15:06:52