前奏
使用 Gson (https://github.com/google/gson)去解析 json 应该是很常见的,大部分的情况下我们只要创建一个
Gson 对象,然后根据 json 和对应的 Java 类去解析就可以了。
![](images/2017070621.png)
但是对于比较复杂的 json,比如下面这种, attributes 对应的 jsonObject
中的字段是完全不一样的,这个时候再简单的用上面的方法就解析不了了。
![](images/2017070622.png)
当然了,我们说一步到位的方式解决不了,但用一点笨方法还是可以的。比如先手动解析拿到 attributes
对应的 jsonObject,根据与它同级 type 对应的 value 就可以判断这一段 jsonObject
对应的 Java 类是哪个,最后就采用 gson.from() 方法解析出 attributes 对应的
![](images/2017070623.png)
虽然这样能实现整个 json 的反序列化,但是这种方式比较麻烦,而且一点也不优雅,如果项目中存在很多这样的情况,就会做很多重复的体力劳动。
如何更优雅、更通用的解决这类问题,在网上没有找到答案,只好去深入研究一下Gson了。带着这样的目的,翻看了Gson的文档(https://github.com/google/gson/blob/master/UserGuide.md),发现了一句话
![](images/2017070624.png)
这句话说 Gson 可以处理任意的 Java 对象。那么对于上面讲的那种反序列化情况来讲, Gson
应该也能做到。通过研究 Gson 的文档,发现可以通过自定义JsonDeserializer的方式来实现解析这种
jsonObject 类型不同的情况。
我们知道,大部分情况下 Gson 是通过直接 new 出来的方式来创建,不过也可以采用 GsonBuilder
这个类去生成 Gson。
![](images/2017070625.png) GsonBuilder 通过 registerTypeAdapter()方法,对目标类进行注册。当序列化或者反序列化目标类的时候就会调用我们注册的typeAdapter,
这样就实现了人工干预 Gson 的序列化和反序列化过程。
GsonBuilder 的 registerTypeAdapte() 方法的第二个参数是 Object
类型,也就意味着我们可以注册多种类型的 typeAdapter,目前支持的类型有 JsonSerializer、JsonDeserializer、InstanceCreator、TypeAdapter。
![](images/2017070626.png)
经过一番捣鼓,写了一个工具类,对于上面的那个复杂 json,用了不到10行代码就搞定,而且比较优雅和通用。
![](images/2017070627.png)
本文就简单分析一下如何通过自定义 JsonDeserializer 来实现一个通用的工具类用于解析复杂类型
json。对于以后碰到相似问题,这种处理方法可以提供一种解决问题的思路。具体的代码和实例,可以查看项目(https://github.com/sososeen09/MultiTypeJsonParser)。如果对您的思路有一些启发,欢迎交流和Star。
JsonDeserializer介绍
JsonDeserializer 是一个接口,使用的时候需要实现这个接口并在 GsonBuilder
中对具体的类型去注册。当反序列化到对应的类的时候就会调用这个自定义 JsonDeserializer
的 deserialize() 方法。下面对这个方法的几个参数做一下解释,以便于更好的理解Gson解析的过程。
![](images/2017070628.png)
JsonElement
JsonElement代表 在 Gson 中的代表一个元素。它是一个抽象类,有4个子类:JsonObject、JsonArray、JsonPrimitive、JsonNull。
Ⅰ.JsonObject 表示的是包含name-value型的 json 字符串,其中 name
是字符串,而 value 可以是其它类型的 JsonElement 元素。在json中用 “{}”
包裹起来的一个整体就是JsonObject。例如
![](images/2017070629.png)
Ⅱ.JsonArray 这个类在 Gson 中代表一个数组类型,一个数组就是JsonElement的集合,这个集合中每一个类型都可能不同。这是一个有序的集合,意味着元素的添加顺序是被维持着的。上面例子中list对应的
“[]” 包裹起来的json就是JsonArray。
Ⅲ.JsonPrimitive 这个可以认为是json中的原始类型的值,包含Java的8个基本类型和它们对应的包装类型,也包含
String 类型。比如上面 "first-name" 对应的 "Su"
就是一个 String 类型的 JsonPrimitive 。
Ⅳ.JsonNull 通过名字也可以猜到,这个代表的是 null 值。
Type
Type是Java中的所有类型的顶层接口,它的子类有 GenericArrayType、ParameterizedType、TypeVariable、WildcardType,这个都是在java.lang.reflect包下面的类。另外,我们最熟悉的一个类
Class 也实现了 Type 接口。
一般来讲,调用 GsonBuilder 的 registerTypeAdapter() 去注册,第一个参数使用
Class 类型就可以了。
JsonDeserializationContext
这个类是在反序列过程中,由其它类调用我们自定义的 JsonDeserialization 的 deserialize()
方法时传递过来的,在 Gson 中它唯一的一个实现是TreeTypeAdapter 中的一个私有的内部类
GsonContextImpl 。可以在自定义的 JsonDeserializer 的 deserialize()
中去调用 JsonDeserializationContext 的 deserialize() 方法去获得一个对象。
但是要记住,如果传递到 JsonDeserializationContext 中的 json 与
JsonDeserializer 中的 json 一样的话,可能会导致死循环调用。
思路分析
创建JavaBean
还是以最上面的那个 json 进行分析,在 list 对应 JsonArray ,其中的两个 JsonObject
中,attributes 对应的 JsonObject 字段完全不一样,但是为了统一,在写 JavaBean
的时候可以给它们设置一个共同的父类,尽管它是空的。
![](images/20170706210.png)
设置 Attribute 这个 SuperClass 只是为了在 GsonBuilder 去注册,当具体解析的时候我们会根据
type 对应的类型去找到对应的Class。
![](images/20170706211.png)
到了这里我们就应该想到,type 对应的 value 肯定是要与具体的 JavaBean 对应起来的。比如在这里就是
![](images/20170706212.png)
如果 type 是 "address" ,那么我们就可以用 gson 去拿 AddressAttribute.class
和对应的 json 去解析。
![](images/20170706213.png)
如何把 json 准确的转为对应的 JavaBean
我们注册的是父类 Attribute ,当反序列化需要解析 Attribute 的时候就会把对应的
json 作为参数回调自定义的 JsonDeserializer 。我们就可以在下面这个方法中写自己的逻辑得到我们需要的
Attribute 对象了。
![](images/20170706215.png)
但是细心的朋友应该会发现了,这个时候传递的 json 有可能是这样的
也有可能是这样的
![](images/20170706216.png)
我们怎么知道该解析成 AddressAttribute 还是 NameAttribute ???
我们想想,具体解析成哪个,我们肯定是需要知道 type 对应的 value 。而这个 type 是与
attributes 同级的字段,照着刚才这样肯定是没希望拿到这个 value 的。
我们再想想,能够知道这个 type 对应的 value 是什么的肯定是 attributes 上一层级的
json 。
![](images/20170706217.png)
那么我们可不可以在 GsonBuilder 中再去注册一个 typeAdapter 来解析这个外层的
json 呢?当然可以。
![](images/20170706218.png)
这个 AttributeWithType 就是外层的 json 对应的 JavaBean
![](images/20170706219.png)
在反序列化 AttributeWithType 这个类的时候,我们可以获得这个 type 对应的
value,然后把这个 value 传递给里层的 Attribute 对应的 JsonDeserializer。这样就可以根据
value 是 “address” 或者 “name” 去对 AddresAttribute 或者
NameAttribute 进行反序列化了。
有一个坑
前面那我们讲过,调用 JsonDeserializationContext 的方法应该注意死循环。在具体的实践中,我虽然没有调用
JsonDeserializationContext 的方法,但是依然出现了死循环的情况。就是因为我是这么用的。
![](images/20170706220.png)
乍一看没什么问题啊,问题就出在这个 gson 身上。这个 gson 是已经注册过解析 AttributeWithType
的 GsonBuilder 创建的。 gson.fromJson() 方法中的 json 是 AttributeWithType
对应的反序列化的 json,gson.fromJson() 内部会再次调用 AttributeWithType
对应的 JsonDeserializer 中的 deserialize() 方法,从而导致死循环。
避免死循环的方式就是用GsonBuilder新建一个 gson ,这个GsonBuilder不再注册
AttributeWithType ,而只去注册 Attribute 去解析。
为了更好更通用
Ⅰ.在项目中,可能还会存在另一种格式的json,外部没有单独的type元素,而是与其它的元素放在同一个JsonObject中。这样的格式更省事,不需要注册外层的typeAdaper即可。
![](images/20170706221.png)
Ⅱ.如果在解析过程中发现有些类型没有注册到 MultiTypeJsonParser 的 Builder
中,解析的时候碰到相应的 jsonObject 就直接返回null。比如下面这样的json中,"type"
对应的 "parents" 如果没有注册,那么反序列化的时候这个 json 所代表的对象就为
null 。
![](images/20170706222.png)
在Android中我们反序列这样的 json 后一般会把得到的对象的设置到列表控件上,如果后端返回的
json 中包含之前未注册的类型,为了程序不至于 crash,需要对反序列化的 null 对象进行过滤,项目中提供了一个工具类
ListItemFilter 可以过滤集合中为 null 的元素。
结语
对于如何优雅的解析这种类型不同的 JsonObject ,刚开始我是缺少思路的,在网上也没有查到合适的文档。但是通过查看
Gson 的文档和源码,通过自己的理解和分析,逐步的完成了这个过程。我的一个感触就是,多去看看官方的使用文档应该比盲目去搜索解决方案更好。
代码是最好的文档,本文只简单介绍了一些实现思路,文中贴出的一些代码是为了讲述方便,与项目中的代码可能会有有些区别。具体的使用可以看项目(https://github.com/sososeen09/MultiTypeJsonParser)中的例子。 |