【轉】采用Gson解析含有多種JsonObject的復雜json


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

0 前奏

使用 Gson 去解析 json 應該是很常見的,大部分的情況下我們只要創建一個 Gson 對象,然后根據 json 和對應的 Java 類去解析就可以了。

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

但是對於比較復雜的 json,比如下面這種, attributes 對應的 jsonObject 中的字段是完全不一樣的,這個時候再簡單的用上面的方法就解析不了了。

{ "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 對象。


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的文檔,發現了一句話

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

這句話說 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 的序列化和反序列化過程。

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

  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官方文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM