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` 注解。### 最后如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。