[Gson]Gson 解析 Json 容错策略-当前焦点
一. 序章
文章评论里后台有一些小伙伴,针对具体数据容错的场景,提出了具体的问题。今天就在这篇文章里统一解答,并且给出解决方案。
二. 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 类中包含 name
和 age
两个字段,其中 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 容错策略-当前焦点
- 1919持续深耕山东市场
- 上海三明两地12家医疗机构签订对口合作协议_世界热文
- A15双核FHD折叠屏三星GALAXYQ现身官网 关注
- 顺反子指的是dna还是rna_顺反子
- 全球观速讯丨眼线是什么意思 眼线是好人还是坏人?
- 我的世界格雷科技6模组控制器覆盖板有什么用 世界速看
- 小学生如何保护视力的方法有哪些_小学生保护视力的方法
- 午评:沪指冲高回落跌0.24% 存储芯片概念股逆势大涨|天天报资讯
- 世界观焦点:总预算17504万元!中国电信启动杭州-金华-合肥等2个干线光缆建设施工集采
- CCTV5直播!广东VS深圳巅峰对决,杜锋全主力复仇,周鹏再遇旧主
- 国际原子能机构总干事:应采取措施保护扎波罗热核电站免受任何攻击-每日看点
- 世界今头条!滇池大泊口海菜花重现
- 【新要闻】小龙虾“遇冷”价格居高不下 预计四月中旬后在申城批量上市
- 热点评!3月30日06时湖南永州疫情新增确诊数及永州现在总共有多少疫情
- 中秋节有感作文800字_中秋节有感
- 国民指挥官什么时候出 公测上线时间预告-全球微资讯
- 观速讯丨厨房小妙招 厨房小妙招锅底脏了怎么办
- 官方发起对哪吒强度的调查,是要重做的信号?网友:优化即可
- 武汉航海职业技术学院在哪里 武汉航海职业技术学院怎么样|全球快播报
- 快资讯:ps模糊工具怎么用_ps模糊工具
- 大健康产业持续升温 巨星传奇产品矩阵日趋丰富
- 北京城建完成发行10亿短期融资券 利率2.1%_环球观天下
- 马云回国引关注,民营企业家重拾信心,还有哪些信号值得关注? 焦点热闻
- 【直播预告】中国教育报·名园现场会【西安交通大学(附属)幼儿园】
- 焦点快看:2023年2月29日四川荣锂科技碳酸锂价格下调
- 当前热点-光谷联交所江城公司举办“全市国有企业价值创造能力提升”专题研讨班
- 苏宁易购零售云公布2023年目标 首批开设500家Super店 天天最新
- 国内首款四座氢内燃机飞机验证机在沈首飞 当前焦点
- 猪八戒的优点有哪些_猪八戒的优点
- 济源示范区: “小兔子”跑出乡村振兴新路子-速递
- 3月份信贷有望延续强势增长 结构或进一步改善 全球动态
- 世界短讯!福斯特:融资净买入860.97万元,融资余额3.68亿元(03-28)
- 极海听雷结局了吗(极海听雷结局)
- 关于英国皇家加冕礼的7个事实——政治和宗教仪式的混合体
- 家长反映学校食堂问题被刑拘 到底是怎么回事呢
- 全球速递!2023成都成华区教资认定体检医院是哪?
- 如东县实验中学校园网(如东县实验中学)|天天滚动
- 湘江新区西湖板块添名校 岳麓一小枫林小学金秋投用_最新快讯
- 世界即时看!减持行为发生日距离减持计划预披露日不足十五个交易日,华平股份股东被出具警示函
- 今日最新疫情实时消息 新增超12.8万例新冠确诊病例,新增死亡病例347例
- 天天快看:萧邦承诺将在精钢腕表生产中使用循环再造精钢
- 给猴子喂食反被扇一巴掌!目击者:人都被打懵了…-天天头条
- 【世界快播报】食品安全有哪些重要性?为什么要重视食品安全?
- 3月28日12时四川巴中疫情最新通告 3月28日12时四川巴中今日疫情最新消息
- 环球今日报丨急性肠胃炎的治疗方法_肠胃炎的治疗方法
- 文字改革出版社_关于文字改革出版社简介|观察
- 农行普惠金融贷款余额突破3万亿元-环球快资讯
- 世界看热讯:手指肌腱断裂恢复需要多长时间_手指肌腱断裂手术多少钱
- 今热点:街头涂鸦:创作还是捣蛋?专家:厘清法律边界给合法涂鸦预留发展空间
- 燃爆全场!“村BA”总决赛完美收官|全球快播报
- 环球热头条丨世乒赛选拔-陈梦0-3遭蒯曼横扫 王楚钦双线获胜
- 欧盟新的维修权指令要求电子厂商提供10年的组件可用性 全球看热讯
- 第二十一届中国国际环保展览会4月举行 800余家企业参展|世界动态
- 笑一笑十年少下一句是信号吗还是感受_笑一笑十年少下一句 天天视点
- 平方米换算亩_平方|今日热议
- 每日热文:民生证券:给予中金黄金买入评级
- 微头条丨南昌经开区:深耕数字经济“新蓝海”
- 图灵波浪3.27-白银回落蓄势、多头或再下一城
- 国际储能市场一片蓝海,铅蓄电池龙头天能股份“走出去”战略初显成效 全球微资讯
- 云南天文台搜寻到155颗大质量脉动变星及其候选体_全球短讯
- 手术室空气检测
- 关注:laughingsir857_laughingsir
- 三本医学院有必要上吗_三本医学院
- 叙利亚外交部:谴责美对叙东部部分地区发动袭击 世界时快讯
- 时讯:“来3斤黄金”!他几乎买空两家金店的金条…店员紧急报警!
- 实时焦点:这个社区有个“周五助老茶话会”
- 武林外史电视剧演员表
- 重生之醒悟_重生之攻刃_全球热头条
- 云舟光影中,缱绻一城山水-环球观热点
- 兰州市城管委紧盯重点问题切实增强综合管理水平
- 【环球快播报】素锦扮演者
- 世界简讯:【好可爱】“熊猫旋风”刮到长春了
- 单位换算表大全初中_单位换算表大全 全球快播
- 全球热文:祉的组词(祉)
- 环球热消息:南方有哪些优秀旅游城市
- 刹字组词拼音(刹的组词和拼音)_热资讯
- 天天热资讯!角钢的符号怎么输入 角钢的符号
- 你知道吗?美国农业居然也有NASA参与其中|全球要闻
- 微速讯:2035.16亿元!今年河南省重点外资项目敲定
- 每日时讯!电脑里的文件彻底删除如何找回
- 欧预赛-姆巴佩梅开二度+助攻 格列兹曼闪击 法国4-0大胜荷兰_全球新要闻
- 广西壮族自治区灵山县发布冰雹橙色预警
- 孕妇鸽子汤禁忌放什么_孕妇鸽子汤禁忌有哪些
- 平治信息(300571)股东郭庆质押777万股,占总股本5.57%|天天报道
- 大天使之剑元素之心怎么得-聚看点
- 台海大变?英国干了件美国都不敢干的事,这次解放军绝不轻饶!-天天新消息
- 天天热门:一起购房后再次出售房屋产权人不愿配合过户纠纷
- 大悦城地产午后升5% 旗下附属公司获准发行40亿元公司债 天天滚动
- 天天观察:架构“重复”,青羊实验中学附小数学组深耕课堂共成长
- 南京蟑螂消杀_蟑螂消杀
- LOL蔚出装_全球播资讯
- 盛业2022年实现净利2.5亿同比增10%,公司首席战略官指出银行端整体对中小微企业支持力度并不充足
- 午间圆桌派
- 2022广东东莞市教育局招聘事业编制教职员(本地场)第六批拟聘人员公示
- 中医养生中的“拉三经”指的是哪三经呢? 全球播报
- 杭州的雨终于要停了,但气温降了
- 方源说金:3.24昨日的多单你跟上了吗?今日早间黄金行情走势分析策略! 时快讯
- 广东省东莞市东华初级中学
- 女人若上了年纪,别犯这几个穿衣搭配的错误,廉价显老又很没品位
广告
广告
- 如何验证翡翠的真假?只需要简单8步 天天短讯
- DJI RS 3 Mini发布:2千克负载仅795克,支持快速竖拍 天天新消息
- 形容法律威严的句子(精选187句)
- 《宝可梦》满血情况下受到的伤害减半,能带来多少对战机会?
- 世界热推荐:活力中国丨在忙碌的生产线感知中国经济活力
- 全球消息!海南航空回应男子在航班上喊飞机要出事:该名旅客已移交机场公安
- 陆金贷(小额应急)网贷逾期3年多久上征信|全球百事通
- 比亚迪继续减持比亚迪股份,半年已减持超30%
- 胎压监测板块1月9日涨0.91%,通达电气领涨,主力资金净流出2377.78万元_环球快消息
- 世界速看:陆金贷(小额应急)贷款逾期八天延迟还款会影响征信吗
- 記者觀察|封關壬寅末終落幕 港深雙城記開新篇 世界微动态
- 南开区16岁小孩抚养费一般多少钱
- 世界微头条丨十来万的车,我选卡罗拉
- 新华视点|商圈火、景区旺 各地消费市场显活力|聚焦
- 每日热门:光猫和路由器怎么连接 光猫和路由器的正确连接方法
- 蔬菜生吃还是熟吃?你是哪一派?|天天观点
- 天天消息!九典制药(300705.SZ):非洛地平片获批上市
- 热水泡脚脚痒是怎么回事?-环球时快讯
- 面试时,最可怕的就是背调?-世界视点
- 环球短讯![快讯]广联航空:关于特定股东减持数量过半的进展