【轉】採用Gson解析含有多種JsonObject的複雜json

本文對應的項目是MultiTypeJsonParser ,項目地址 https://github.com/sososeen09/MultiTypeJsonParsercss

0 前奏

使用 Gson 去解析 json 應該是很常見的,大部分的狀況下咱們只要建立一個 Gson 對象,而後根據 json 和對應的 Java 類去解析就能夠了。java

Gson gson = new Gson();
Person person = gson.form(json,Person.class);

可是對於比較複雜的 json,好比下面這種, attributes 對應的 jsonObject 中的字段是徹底不同的,這個時候再簡單的用上面的方法就解析不了了。python

{ "total": 2, "list": [ { "type": "address", "attributes": { "street": "NanJing Road", "city": "ShangHai", "country": "China" } }, { "type": "name", "attributes": { "first-name": "Su", "last-name": "Tu" } } ] } 

固然了,咱們說一步到位的方式解決不了,但用一點笨方法仍是能夠的。好比先手動解析拿到 attributes 對應的 jsonObject,根據與它同級 type 對應的 value 就能夠判斷這一段 jsonObject 對應的 Java 類是哪一個,最後就採用 gson.from() 方法解析出 attributes 對應的 Java 對象。git

ListInfoWithType listInfoWithType = new ListInfoWithType(); //建立 org.json 包下的 JSONObject 對象 JSONObject jsonObject = new JSONObject(TestJson.TEST_JSON_1); int total = jsonObject.getInt("total"); //建立 org.json 包下的 JSONArray 對象 JSONArray jsonArray = jsonObject.getJSONArray("list"); Gson gson = new Gson(); List<AttributeWithType> list = new ArrayList<>(); //遍歷 for (int i = 0; i < jsonArray.length(); i++) { JSONObject innerJsonObject = jsonArray.getJSONObject(i); Class<? extends Attribute> clazz; String type = innerJsonObject.getString("type"); if (TextUtils.equals(type, "address")) { clazz = AddressAttribute.class; } else if (TextUtils.equals(type, "name")) { clazz = NameAttribute.class; } else { //有未知的類型就跳過 continue; } AttributeWithType attributeWithType = new AttributeWithType(); //採用Gson解析 Attribute attribute = gson.fromJson(innerJsonObject.getString("attributes"), clazz); attributeWithType.setType(type); attributeWithType.setAttributes(attribute); list.add(attributeWithType); } listInfoWithType.setTotal(total); listInfoWithType.setList(list); 

雖然這樣能實現整個 json 的反序列化,可是這種方式比較麻煩,並且一點也不優雅,若是項目中存在不少這樣的狀況,就會作不少重複的體力勞動。
如何更優雅、更通用的解決這類問題,在網上沒有找到答案,只好去深刻研究一下Gson了。帶着這樣的目的,翻看了Gson的文檔,發現了一句話github

Gson can work with arbitrary Java objects including pre-existing objects that you do not have source code of.json

這句話說 Gson 能夠處理任意的 Java 對象。那麼對於上面講的那種反序列化狀況來說, Gson 應該也能作到。經過研究 Gson 的文檔,發現能夠經過自定義JsonDeserializer的方式來實現解析這種 jsonObject 類型不一樣的狀況。後端

咱們知道,大部分狀況下 Gson 是經過直接 new 出來的方式來建立,不過也能夠採用 GsonBuilder 這個類去生成 Gson。數組

Gson gson = new GsonBuilder()
   .registerTypeAdapter(Id.class, new IdTypeAdapter())
   .enableComplexMapKeySerialization()
   .serializeNulls()
   .setDateFormat(DateFormat.LONG)
   .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
   .setPrettyPrinting()
   .setVersion(1.0)
   .create();

GsonBuilder 經過 registerTypeAdapter()方法,對目標類進行註冊。當序列化或者反序列化目標類的時候就會調用咱們註冊的typeAdapter, 這樣就實現了人工干預 Gson 的序列化和反序列化過程。bash

GsonBuilder 的 registerTypeAdapte() 方法的第二個參數是 Object 類型,也就意味着咱們能夠註冊多種類型的 typeAdapter,目前支持的類型有 JsonSerializer、JsonDeserializer、InstanceCreator、TypeAdapter。ide

public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) 

通過一番搗鼓,寫了一個工具類,對於上面的那個複雜 json,用了不到10行代碼就搞定,並且比較優雅和通用。

MultiTypeJsonParser<Attribute> multiTypeJsonParser = new MultiTypeJsonParser.Builder<Attribute>() .registerTypeElementName("type") .registerTargetClass(Attribute.class) .registerTargetUpperLevelClass(AttributeWithType.class) .registerTypeElementValueWithClassType("address", AddressAttribute.class) .registerTypeElementValueWithClassType("name", NameAttribute.class) .build(); ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class); 

本文就簡單分析一下如何經過自定義 JsonDeserializer 來實現一個通用的工具類用於解析複雜類型 json。對於之後碰到類似問題,這種處理方法能夠提供一種解決問題的思路。具體的代碼和實例,能夠查看項目。若是對您的思路有一些啓發,歡迎交流和Star。

1 JsonDeserializer介紹

JsonDeserializer 是一個接口,使用的時候須要實現這個接口並在 GsonBuilder 中對具體的類型去註冊。當反序列化到對應的類的時候就會調用這個自定義 JsonDeserializer 的 deserialize() 方法。下面對這個方法的幾個參數作一下解釋,以便於更好的理解Gson解析的過程。

public interface JsonDeserializer<T> { public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException; } 

1.1 JsonElement

JsonElement表明 在 Gson 中的表明一個元素。它是一個抽象類,有4個子類:JsonObject、JsonArray、JsonPrimitive、JsonNull。
1.JsonObject 表示的是包含name-value型的 json 字符串,其中 name 是字符串,而 value 能夠是其它類型的 JsonElement 元素。在json中用 「{}」 包裹起來的一個總體就是JsonObject。例如

// "attributes" 是name,後面跟着的{}內容是它對應的value,而這個value就是一個JsonObject
  "attributes": {
                  "first-name": "Su",
                  "last-name": "Tu"
                 }

2.JsonArray 這個類在 Gson 中表明一個數組類型,一個數組就是JsonElement的集合,這個集合中每個類型均可能不一樣。這是一個有序的集合,意味着元素的添加順序是被維持着的。上面例子中list對應的 「[]」 包裹起來的json就是JsonArray。

3.**JsonPrimitive ** 這個能夠認爲是json中的原始類型的值,包含Java的8個基本類型和它們對應的包裝類型,也包含 String 類型。好比上面 "first-name" 對應的 "Su" 就是一個 String 類型的 JsonPrimitive 。

4.JsonNull 經過名字也能夠猜到,這個表明的是 null 值。

1.2 Type

Type是Java中的全部類型的頂層接口,它的子類有 GenericArrayType、ParameterizedType、TypeVariable、WildcardType,這個都是在java.lang.reflect包下面的類。另外,咱們最熟悉的一個類 Class 也實現了 Type 接口。

通常來說,調用 GsonBuilder 的 registerTypeAdapter() 去註冊,第一個參數使用 Class 類型就能夠了。

1.3 JsonDeserializationContext

這個類是在反序列過程當中,由其它類調用咱們自定義的 JsonDeserialization 的 deserialize() 方法時傳遞過來的,在 Gson 中它惟一的一個實現是TreeTypeAdapter 中的一個私有的內部類 GsonContextImpl 。能夠在自定義的 JsonDeserializer 的 deserialize() 中去調用 JsonDeserializationContext 的 deserialize() 方法去得到一個對象。

可是要記住,若是傳遞到 JsonDeserializationContext 中的 json 與 JsonDeserializer 中的 json 同樣的話,可能會致使死循環調用。

2 思路分析

2.1 建立JavaBean

仍是以最上面的那個 json 進行分析,在 list 對應 JsonArray ,其中的兩個 JsonObject 中,attributes 對應的 JsonObject 字段徹底不同,可是爲了統一,在寫 JavaBean 的時候能夠給它們設置一個共同的父類,儘管它是空的。

public class Attribute { ... } public class AddressAttribute extends Attribute { private String street; private String city; private String country; ... 省略get/set } public class NameAttribute extends Attribute { @SerializedName("first-name") private String firstname; @SerializedName("last-name") private String lastname; ...省略get/set } 

設置 Attribute 這個 SuperClass 只是爲了在 GsonBuilder 去註冊,當具體解析的時候咱們會根據
type 對應的類型去找到對應的Class。

gsonBuilder.registerTypeAdapter(Attribute.class, new AttributeJsonDeserializer()); 

到了這裏咱們就應該想到,type 對應的 value 確定是要與具體的 JavaBean 對應起來的。好比在這裏就是

"address"——AddressAttribute.class "name"——NameAttribute.class 

若是 type 是 "address" ,那麼咱們就能夠用 gson 去拿 AddressAttribute.class 和對應的 json 去解析。

Attribute attribute = gson.form(addressJson,AddressAttribute.class); 

2.2 如何把 json 準確的轉爲對應的 JavaBean

咱們註冊的是父類 Attribute ,當反序列化須要解析 Attribute 的時候就會把對應的 json 做爲參數回調自定義的 JsonDeserializer 。咱們就能夠在下面這個方法中寫本身的邏輯獲得咱們須要的 Attribute 對象了。

public Attribute deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)

可是細心的朋友應該會發現了,這個時候傳遞的 json 有多是這樣的

{ "street": "NanJing Road", "city": "ShangHai", "country": "China" } 

也有多是這樣的

{ "first-name": "Su", "last-name": "Tu" } 

咱們怎麼知道該解析成 AddressAttribute 仍是 NameAttribute ???

咱們想一想,具體解析成哪一個,咱們確定是須要知道 type 對應的 value 。而這個 type 是與 attributes 同級的字段,照着剛纔這樣確定是沒但願拿到這個 value 的。

咱們再想一想,可以知道這個 type 對應的 value 是什麼的確定是 attributes 上一層級的 json 。

{
   "type": "name",
   "attributes": {
                          ...
                 }  
}

那麼咱們可不能夠在 GsonBuilder 中再去註冊一個 typeAdapter 來解析這個外層的 json 呢?固然能夠。

gsonBuilder.registerTypeAdapter(AttributeWithType.class, new AttributeWithTypeJsonDeserializer()); 

這個 AttributeWithType 就是外層的 json 對應的 JavaBean

public class AttributeWithType { private String type; private Attribute attributes; ... } 

在反序列化 AttributeWithType 這個類的時候,咱們能夠得到這個 type 對應的 value,而後把這個 value 傳遞給裏層的 Attribute 對應的 JsonDeserializer。這樣就能夠根據 value 是 「address」 或者 「name」 去對 AddresAttribute 或者 NameAttribute 進行反序列化了。

2.3 有一個坑

前面那咱們講過,調用 JsonDeserializationContext 的方法應該注意死循環。在具體的實踐中,我雖然沒有調用 JsonDeserializationContext 的方法,可是依然出現了死循環的狀況。就是由於我是這麼用的。

AttributeWithType attributeWithType = gson.fromJson(json, AttributeWithType.class); 

乍一看沒什麼問題啊,問題就出在這個 gson 身上。這個 gson 是已經註冊過解析 AttributeWithType 的 GsonBuilder 建立的。 gson.fromJson() 方法中的 json 是 AttributeWithType 對應的反序列化的 json,gson.fromJson() 內部會再次調用 AttributeWithType 對應的 JsonDeserializer 中的 deserialize() 方法,從而致使死循環。

避免死循環的方式就是用GsonBuilder新建一個 gson ,這個GsonBuilder再也不註冊 AttributeWithType ,而只去註冊 Attribute 去解析。

3 爲了更好更通用

1.在項目中,可能還會存在另外一種格式的json,外部沒有單獨的type元素,而是與其它的元素放在同一個JsonObject中。這樣的格式更省事,不須要註冊外層的typeAdaper便可。

{ "total": 2, "list": [ { "type": "address", "street": "NanJing Road", "city": "ShangHai", "country": "China" }, { "type": "name", "first-name": "Su", "last-name": "Tu" } ] } MultiTypeJsonParser<Attribute> multiTypeJsonParser = new MultiTypeJsonParser.Builder<Attribute>() .registerTypeElementName("type") .registerTargetClass(Attribute.class) // 若是所要解析的 jsonObejct 中已經含有可以表示自身類型的字段,不須要註冊外層 Type,這樣更省事 // .registerTargetUpperLevelClass(AttributeWithType.class) .registerTypeElementValueWithClassType("address", AddressAttribute.class) .registerTypeElementValueWithClassType("name", NameAttribute.class) .build(); ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class); 

2.若是在解析過程當中發現有些類型沒有註冊到 MultiTypeJsonParser 的 Builder 中,解析的時候碰到相應的 jsonObject 就直接返回null。好比下面這樣的json中,"type" 對應的 "parents" 若是沒有註冊,那麼反序列化的時候這個 json 所表明的對象就爲 null 。

{ "type": "parents", "attributes": { "mather": "mi lan", "father": "lin ken" } } 

在Android中咱們反序列這樣的 json 後通常會把獲得的對象的設置到列表控件上,若是後端返回的 json 中包含以前未註冊的類型,爲了程序不至於 crash,須要對反序列化的 null 對象進行過濾,項目中提供了一個工具類 ListItemFilter 能夠過濾集合中爲 null 的元素。

4 結語

對於如何優雅的解析這種類型不一樣的 JsonObject ,剛開始我是缺乏思路的,在網上也沒有查到合適的文檔。可是經過查看 Gson 的文檔和源碼,經過本身的理解和分析,逐步的完成了這個過程。個人一個感觸就是,多去看看官方的使用文檔應該比盲目去搜索解決方案更好。

代碼是最好的文檔,本文只簡單介紹了一些實現思路,文中貼出的一些代碼是爲了講述方便,與項目中的代碼可能會有有些區別。具體的使用能夠看項目中的例子。

若是有問題,歡迎提 issue 或留言,若是對您有所幫助,歡迎Star。

參考

Gson官方文檔

相關文章
相關標籤/搜索