文章目录
- 基于 Gson 的 Json 数据解析库
- 1. 背景
- 2. 使用
- 2.1 在模块下的 `build.gradle` 文件中加入远程依赖
- 2.2 常规使用
- 2.3 其它 API
- 2.3 Retrofit 替换 Gson
- 3. Json 数据解析库 UML 类图与时序图
- 3.1 json-parse 类图
- 3.2 json-parse 时序图
- 4. Json 工具类(JsonUtils)
- 5. Json 解析容错兜底(GsonFactory)
- 5.1 容错支持的数据类型
- 5.2 容错处理规则
- 5.3 GsonFactory 容错工厂适配器
- 6. Json 异常上报
- 7. Json 解析库 AB Test
- 8. Json 脏数据纠正
- 9. Json 解析库容错覆盖(单元测试)
- 10. 探讨
- 10.1 有没有必要处理 Json 解析容错兜底?
- 10.2 如果后端用的是 Java,还有必要处理容错吗?
- 10.3 iOS 和前端是如何进行 Json 数据解析?
- 10.3.1 iOS 端如何进行 Json 数据解析?
- 10.3.2 前端如何进 Json 数据解析?
- 11 参考资料
基于 Gson 的 Json 数据解析库
1. 背景
- 不知道大家在开发的过程中有没有遇见过
Json
数据解析异常等错误,咱们分析其原因大多数是因为其返回的 Json
数据结构与预期的数据结构不一致导致的,而数据结构不一致的来源主要有两点: - (1)后端返回的
Json
数据结构与预期的 Json
数据结构不一致,或者存在某些 bug
导致返回的 Json
数据结构不一致; - (2)某个字段后端接收的就是一个
Json 字符串
,且后端并不关心这个字段具体的内容,而这个 Json 字符串
是由业务端生成(Andorid
、iOS
、前端
、PC
),然而多端定义不统一或者某端 bug
也会导致返回的 Json
数据结构不一致。 - 针对于上述的业务背景,我们希望该解析库能做到如下功能:
- (1)统一项目中
Json
数据解析的调用,从而方便统一调用来源; - (2)针对于某些业务场景有对应的
Json
解析容错兜底方案,以防止因为某些字段数据结构错误而导致整个数据类无法生成; - (3)对应的
Json
异常上报机制,能够方便追溯线下线上存在的 Json
数据结构异常问题。
2. 使用
2.1 在模块下的 build.gradle
文件中加入远程依赖
dependencies {
// Json 解析库
implementation 'cn.eeo.android.core:json-parse:1.0.0-alpha01'
}
- 需要注意的是:该 Json 解析库使用的
Gson
框架是基于 2.10
版本, 请保持版本统一,否则将会出现版本兼容问题;
2.2 常规使用
- 使用
JsonUtils
类中的方法即可满足需求,具体可看后续的第 4 点;
2.3 其它 API
GsonFactory.setSingletonGson(Gson)
val gsonBuilder : GsonBuilder = GsonFactory.newGsonBuilder();
GsonFactory.registerTypeAdapterFactory(TypeAdapterFactory)
GsonFactory.registerInstanceCreator(Type, InstanceCreator<*>)
GsonFactory.addReflectionAccessFilter(ReflectionAccessFilter)
2.3 Retrofit 替换 Gson
val retrofit: Retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(GsonFactory.getSingletonGson()))
.build()
- 需要注意的是:
Retrofit
替换会不会影响到整体范围,因为容错也不是万能,需要具体业务具体分析。
3. Json 数据解析库 UML 类图与时序图
3.1 json-parse 类图
3.2 json-parse 时序图
4. Json 工具类(JsonUtils)
方法名 | 含义 |
---|
Gson createDefaultGson() | 创建并返回默认的 Gson ,具有容错兜底机制 |
setGson(String, Gson) | 设置 Gson 对象 |
Gson getGson | 获取默认的 Gson |
Gson getGson(String) | 根据 key 键获取并返回 Gson |
String toJson(Any) 、String toJson(Any, Type) 、String toJson(Gson, Any) 、String toJson(Gson, Any, Type) | 对象转 Json 串 |
T fromJson(String, Class<T>) 、T fromJson(String, Type) 、T fromJson(Reader, Class<T>) 、T fromJson(Reader, Type) 、T fromJson(Gson, String, Class<T>) 、T fromJson(Gson, String, Type) 、T fromJson(Gson, Reader, Class<T>) 、T fromJson(Gson, Reader, Type) | Json 串转对象 |
Type getListType(Type) | 获取链表类型 |
Type getSetType(Type) | 获取集合类型 |
Type getMapType(Type, Type) | 获取字典类型 |
Type getArrayType(Type) | 获取数组类型 |
Type getType(Type, Type) | 获取类型 |
void setJsonCallback(JsonCallback) | 设置 Json 解析异常监听器 |
JsonCallback getJsonCallback() | 获取并返回 Json 解析异常监听器 |
5. Json 解析容错兜底(GsonFactory)
5.1 容错支持的数据类型
支持的数据类型 | 含义 |
---|
String | 字符串 |
boolean/Boolean | 布尔值 |
int/Integer | 整数,属于数值类 |
long/Long | 长整数,属于数值类 |
float/Float | 单精度浮点数,属于数值类 |
double/Double | 双精度浮点数,属于数值类 |
BigDecimal | 精度更高的浮点数,属于数值类 |
Array | 数组集合 |
Bean | Bean 类 |
Map | Map 集合 |
JSONArray | |
JSONObject | |
5.2 容错处理规则
- 大家可能觉得
Gson
解析容错没什么,那是因为我们对 Gson
解析失败的场景没有了解过: 惊吓不断
:后端有数据时返回 JsonObject
类型,没数据返回 [],Gson
会直接抛出异常;意想不到
:如果客户端定义的是 boolean
类型,但是后台返回的是 0 或者 1,Gson
会直接抛出异常;措手不及
:如果客户端定义的是 int
或者 long
类型,但是后台返回的是 float
或者 double
类型,Gson
会直接抛出异常。- 基于上述,定义了如下容错处理规则:
- (1)如果后端返回的类型和客户端定义的类型不匹配,框架就跳过解析这个字段,不影响其它字段正常解析;
- (2)如果客户端定义的是
boolean
类型,但是后台返回整数,框架则将非 0 的数值则赋值为 true
,否则为 false
; - (3)如果客户端定义的是
int
或者 long
类型,但后台返回浮点数,框架就对数值进行直接取整并赋值给字段。
5.3 GsonFactory 容错工厂适配器
class GsonFactory private constructor() {
companion object {
private val INSTANCE_CREATORS = ArrayMap<Type, InstanceCreator<*>>(0)
private val TYPE_ADAPTER_FACTORIES: MutableList<TypeAdapterFactory> = mutableListOf()
private val REFLECTION_ACCESS_FILTERS: MutableList<ReflectionAccessFilter> = mutableListOf()
@Volatile
private var singletonGson: Gson? = null
fun getSingletonGson(): Gson =
singletonGson ?: synchronized(this) {
singletonGson ?: newGsonBuilder().create().also {
singletonGson = it
}
}
fun setSingletonGson(gson: Gson) {
this.singletonGson = gson
}
fun newGsonBuilder(): GsonBuilder {
val gsonBuilder = GsonBuilder()
if (! BuildConfig.DEBUG) {
val constructor = ConstructorConstructor(INSTANCE_CREATORS, true, REFLECTION_ACCESS_FILTERS)
gsonBuilder.registerTypeAdapterFactory(TypeAdapters.newFactory(String::class.java, StringTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(Boolean::class.javaPrimitiveType, Boolean::class.java, BooleanTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(Int::class.javaPrimitiveType, Int::class.java, IntegerTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(Long::class.javaPrimitiveType, Long::class.java, LongTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(Float::class.javaPrimitiveType, Float::class.java, FloatTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(Double::class.javaPrimitiveType, Double::class.java, DoubleTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(BigDecimal::class.java, BigDecimalTypeAdapter()))
.registerTypeAdapterFactory(CollectionTypeAdapterFactory(constructor))
.registerTypeAdapterFactory(ReflectiveTypeAdapterFactory(constructor, FieldNamingPolicy.IDENTITY, Excluder.DEFAULT))
.registerTypeAdapterFactory(MapTypeAdapterFactory(constructor, false))
.registerTypeAdapterFactory(TypeAdapters.newFactory(JSONObject::class.java, JSONObjectTypeAdapter()))
.registerTypeAdapterFactory(TypeAdapters.newFactory(JSONArray::class.java, JSONArrayTypeAdapter()))
for (typeAdapterFactory in TYPE_ADAPTER_FACTORIES) {
gsonBuilder.registerTypeAdapterFactory(typeAdapterFactory)
}
}
return gsonBuilder
}
fun registerTypeAdapterFactory(factory: TypeAdapterFactory) {
TYPE_ADAPTER_FACTORIES.add(factory)
}
fun registerInstanceCreator(type: Type, creator: InstanceCreator<*>) {
INSTANCE_CREATORS[type] = creator
}
fun addReflectionAccessFilter(filter: ReflectionAccessFilter) {
REFLECTION_ACCESS_FILTERS.add(0, filter)
}
}
}
6. Json 异常上报
- 自定义
Exception
,取名为 JsonException
,用作于 Json
解析异常上报的 Exception
,如下所示:
class JsonException: Exception {
constructor(message: String): super(message)
constructor(cause: Throwable): super(cause)
constructor(message: String, cause: Throwable): super(message, cause)
}
- 自定义
Callback
,取名为 JsonCallback
,用作于 Json
解析异常监听器,如下所示:
interface JsonCallback {
fun onTypeException(typeToken: TypeToken<*>?, fieldName: String?, jsonToken: JsonToken?)
fun onToJsonException(methodName: String, any: Any?, typeOfAny: Type?, cause: Throwable?)
fun onFromJsonException(methodName: String, json: String?, type: Type?, cause: Throwable?)
fun onGetTypeException(methodName: String, type1: Type?, vararg type2: Type?, cause: Throwable?)
}
- 自定义
Json
解析库对应的初始化器,取名为 JsonInitializer
,用作于初始化 JsonCallback
使用,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.eeo.json">
<application>
<meta-data
android:name="cn.eeo.json.JsonInitializer"
android:value="cn.eeo.initialize" />
</application>
</manifest>
class JsonInitializer: Initializer {
override fun dependencies(): List<Class<out Initializer>> = emptyList()
override fun initialize(context: Context) {
JsonUtils.setJsonCallback(object: JsonCallback {
override fun onTypeException(typeToken: TypeToken<*>?, fieldName: String?, jsonToken: JsonToken?) {
JsonUtils.logger.error("onTypeException \ntypeToken:$typeToken \nfieldName:$fieldName \njsonToken:$jsonToken")
CrashReport.postException(JsonException("onTypeException typeToken:$typeToken fieldName:$fieldName jsonToken:$jsonToken"))
}
override fun onToJsonException(methodName: String, any: Any?, typeOfAny: Type?, cause: Throwable?) {
JsonUtils.logger.error("onToJsonException \nmethodName:$methodName \nany:$any \ntypeOfAny:$typeOfAny \ncause:$cause")
CrashReport.postException(JsonException("onToJsonException methodName:$methodName any:$any typeOfAny:$typeOfAny cause:$cause"))
}
override fun onFromJsonException(methodName: String, json: String?, type: Type?, cause: Throwable?) {
JsonUtils.logger.error("onFromJsonException \nmethodName:$methodName \njson:$json \ntype:$type \ncause:$cause")
CrashReport.postException(JsonException("onFromJsonException methodName:$methodName json:$json type:$type cause:$cause"))
}
override fun onGetTypeException(methodName: String, type1: Type?, vararg type2: Type?, cause: Throwable?) {
JsonUtils.logger.error("onGetTypeException \nmethodName:$methodName \ntype1:$type1 \ntype2:$type2 \ncause:$cause")
CrashReport.postException(JsonException("onGetTypeException methodName:$methodName type1:$type1 type2:$type2 cause:$cause"))
}
})
}
}
7. Json 解析库 AB Test
线下环境的 AB Test
:debug
包下采用非容错兜底机制的 GsonBuilder
构建 Gson
去进行 Json
解析,而非 debug
包下采用容错兜底机制的 GsonBuilder
构建 Gson
去进行 Json
解析,其目的是希望如果存在必现错误,即可在开发阶段暴露出来,从而去发现与解决;线上环境的 AB Test
:- 通过服务器下发的字段配置,去
enable
或 disable
容错兜底机制,从而实现线上的 AB Test
。
8. Json 脏数据纠正
- 错误的
Json
数据结构咱们该如何纠正? 第一步
:我们需要定位到错误的 Json
数据结构产生的来源,到底是后端接口存在的问题,还是其它端生产的错误 Json
数据,只有解决了来源,来能阻止线上继续产生新的脏数据;第二步
:其次去对线上已有的脏数据进行数据结构纠正,这个就要看到底是业务端针对某些特殊场景去进行纠正操作,还是后端对脏数据进行分析后通过对应的脚本去进行具体脏数据的精准纠正操作。
9. Json 解析库容错覆盖(单元测试)
- 通过上述可知,其支持容错的数据类型基本涵盖 99.99% 的开发场景,但是如何进行容错覆盖验证呢?
- 这就需要借助咱们的单元测试来进行操作了,通过全面的
case
再结合具体的单元测试用例 ,即可完成容错覆盖验证了。 - 具体可运行该库中的单元测试用例来查看效果:
数据类型 | 容错的范围 | 数据示例 |
---|
字符串 | bean 、集合、null 、布尔值、数值 | {}、[]、null 、false 、0 |
布尔值 | bean 、集合、null 、字符串、数值 | {}、[]、null 、“”、0 |
数值 | bean 、集合、null 、字符串、布尔值 | {}、[]、null 、“”、false |
集合 | bean 、null 、字符串、布尔值、数值 | {}、null 、“”、false 、0 |
bean | 集合、null 、字符串、布尔值、数值 | []、null 、“”、false 、0 |
10. 探讨
10.1 有没有必要处理 Json 解析容错兜底?
- 个人觉得这个问题没有具体的答案。按照理想的状态应该是不需要进行
Json
解析容错兜底的,但是理想很丰满现实很骨感,因为后端返回的数据结构或其它端生产的数据结构是什么样的我们无法把控,但是有一点是可以肯定的,我们都希望它不出现问题,因为返回的 Json
数据结构存在问题而导致页面数据丢失或者整个应用 崩溃,这是我们最不想见到的现象。当然 Json
解析容错兜底也不是万能的,我始终相信没有最好的方案,只有最合适的方案,所以这个需要根据具体的业务场景选定具体的实现方案。
10.2 如果后端用的是 Java,还有必要处理容错吗?
- 如果后端用的是
PHP
,那么十分推荐你使用这个框架,因为 PHP
返回的数据结构很乱,经历过的同学都懂;如果后端用的是 Java
,那么就根据实际情况而定。不管后端用的是什么,咱们都秉承一个原则具体问题具体分析。 - 但是最怕一种现象是,
Json
数据结构存在问题,前端没问题,iOS
没问题,为什么 Android
端有问题?然后一问其它端的同学,iOS
做了兜底,前端也做了兜底,就 Android
端没做兜底,Android
为什么不做?总不能说咱们用的谷歌 Gson
解析框架它的机制本身就是这样的。虽说问题产生的原因不是 Android
端,但是对于用户使用感知存在的问题就是 Android
端,这就是很难受的事情了。
10.3 iOS 和前端是如何进行 Json 数据解析?
10.3.1 iOS 端如何进行 Json 数据解析?
iOS
端使用的是 NSJSONSerialization 解析器
,从而实现 JSON
和 foundation object
之间的相互转换;参考链接- 需要注意的是:
iOS
的数值类型(Int
、Long
、Float
、Double
…)它属于弱类型转换,例如规约的是 Int
类型字段,下发了一个 Double
类型,这样在 iOS
端不会存在问题。
10.3.2 前端如何进 Json 数据解析?
- 前端使用的是
JSON
解析器,从而实现 JSON
和 JavaScript
对象之间的互相转换;参考链接
11 参考资料
- Gson-github
- AndroidUtilCode-github
- GsonFactory-github