json数组解析(json详解)-编程之家

一. 序

前几天,利用GSON写了关于JSON序列化和反序列化之间的数据容错问题。 最简单的方法是使用@SerializedName注释设置多个不同的JSON Key值,或使用@Expose设置一些异常。 更复杂的数据可以使用类型适配器解决。 类型适配器可以说是GSON分析JSON的银子弹。 所有复杂的数据分析和容错问题都可以通过它解决。

后台有几个伙伴,针对具体的数据容错场景,提出了具体的问题。 今天用这篇文章统一解答,给出解决方案。

二. GSON 数据容错实例

如上所述,GSON为数据容错处理提供了简单的注释。 要进行更复杂的操作,需要使用类型适配器。 需要注意的是,访问TypeAdapter时,注释的放置会无效。

2.1什么是类型适配器

TypeAdapter是gson2. 1版中开始支持的抽象类,用于继承特定类型的序列化和反序列化。 类型适配器最重要的两种方法是write (和read ),分别继承序列化和反序列化的具体过程。

如果要单独继承序列化或反序列化进程,可以使用两个接口: JSON序列化程序和JSON序列化程序。 这些接口的组合效果类似于类型适配器,但内部实现不同。

简单来说,TypeAdapter支持流式传输,因此节省了内存,但很难使用。 另一方面,由于JSON序列器和JSON序列器在操作之前会将所有数据加载到内存中,所以内存比TypeAdapter要高,但是API可以使用得更明确。

虽然类型适配器可以节省内存,但是业务接口中使用的数据量通常不会产生明显的影响,实际上可以忽略。

类型适配器、JSON序列器和JSON序列器都需要与GSO nbuilder.registertypeadapter (() ) ) )的方法配合使用,因此在本文中使用

2.2空字符串等于0

对于一些强类型变换,GSON本身具有一些缺省容错机制。 例如,将字符串“18”转换为Java整数类型的18。 默认情况下支持这一点。

例如,我有记录用户信息的用户类。

类用户

虚拟机名称=’ ‘

var age=0

复盖函数字符串() :字符串{ }

返回’ ‘

{

“名称”: $ {名称}”,

‘ age’:${age}

}

“.修剪索引()”。

}

}

User类包含两个字段: name和age。 其中,与age对应的JSON类型为18或’ 18 ‘。 这是允许的。

{

‘ name’: ‘承香墨影’、

‘ age’:18 //’age’:’18 ”

}

如果该服务器说该用户没有填写年龄信息,所以直接返回了空字符串“”,此时客户端用Gson进行解析将是悲剧。

这当然是服务器端的问题,如果数据明确是Int型的话,默认值也应该是0或-1。

但是,如果遇到这种情况,请用默认的GSON战略进行解析,以获得Crash。

caused by : com .谷歌. gson.jsonsyntaxexception 3360

–Java.lang.numberformatexception :

– -空字符串

出乎意料的是Crash,让我们来看看如何解决这种数据容错问题吧?

在此场景中,由于只需要执行反序列化操作,因此可以实现JSON序列化接口,并继承Int类型。 让我直接举个例子。

classintdefaut 0适配器3360 jsondeserializerint {

overridefundeserialize (JSON : JSON元件?

类型:

上下文: JSondeserialization上下文? ) :英寸{2}

国际航空(日本航空)? getAsString ().equals ) ‘) ) )

返回0

}

特里

返回约翰逊! getAsInt () )。

catch (e :编号格式描述) {

返回0

}

}

}

fun int默认设置

t0(){
val jsonStr = “””
{
“name”:”承香墨影”,
“age”:””
}
“””.trimIndent()
val user = GsonBuilder()
.registerTypeAdapter(
Int::class.java,
IntDefaut0Adapter())
.create()
.fromJson<User>(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<String>()

在 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<User>(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,这些从命名上就很好理解了,就不过多介绍了。

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

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

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

class UserGsonAdapter:JsonDeserializer<User>{
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<User>(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 注解。

就这样吧,有问题在推jqdxxm末留言。

本文对你有帮助吗?留言、点赞、转发是最大的支持,谢谢!