本文對應的項目是MultiTypeJsonParser 女揭,項目地址 https://github.com/sososeen09/MultiTypeJsonParser
0 前奏
使用 Gson 去解析 json 應該是很常見的子姜,大部分的情況下我們只要創(chuàng)建一個 Gson 對象,然后根據(jù) 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芙扎,根據(jù)與它同級 type 對應的 value 就可以判斷這一段 jsonObject 對應的 Java 類是哪個,最后就采用 gson.from()
方法解析出 attributes 對應的 Java 對象填大。
ListInfoWithType listInfoWithType = new ListInfoWithType();
//創(chuàng)建 org.json 包下的 JSONObject 對象
JSONObject jsonObject = new JSONObject(TestJson.TEST_JSON_1);
int total = jsonObject.getInt("total");
//創(chuàng)建 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);
雖然這樣能實現(xiàn)整個 json 的反序列化纵顾,但是這種方式比較麻煩,而且一點也不優(yōu)雅栋盹,如果項目中存在很多這樣的情況,就會做很多重復的體力勞動敷矫。
如何更優(yōu)雅例获、更通用的解決這類問題,在網(wǎng)上沒有找到答案曹仗,只好去深入研究一下Gson了榨汤。帶著這樣的目的,翻看了Gson的文檔怎茫,發(fā)現(xiàn)了一句話
Gson can work with arbitrary Java objects including pre-existing objects that you do not have source code of.
這句話說 Gson 可以處理任意的 Java 對象收壕。那么對于上面講的那種反序列化情況來講, Gson 應該也能做到轨蛤。通過研究 Gson 的文檔蜜宪,發(fā)現(xiàn)可以通過自定義JsonDeserializer的方式來實現(xiàn)解析這種 jsonObject 類型不同的情況。
我們知道祥山,大部分情況下 Gson 是通過直接 new 出來的方式來創(chuàng)建圃验,不過也可以采用 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斧散, 這樣就實現(xiàn)了人工干預 Gson 的序列化和反序列化過程。
GsonBuilder 的 registerTypeAdapte() 方法的第二個參數(shù)是 Object 類型摊聋,也就意味著我們可以注冊多種類型的 typeAdapter鸡捐,目前支持的類型有 JsonSerializer、JsonDeserializer麻裁、InstanceCreator箍镜、TypeAdapter。
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter)
經(jīng)過一番搗鼓悲立,寫了一個工具類鹿寨,對于上面的那個復雜 json,用了不到10行代碼就搞定薪夕,而且比較優(yōu)雅和通用脚草。
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 來實現(xiàn)一個通用的工具類用于解析復雜類型 json。對于以后碰到相似問題原献,這種處理方法可以提供一種解決問題的思路馏慨。具體的代碼和實例,可以查看項目姑隅。如果對您的思路有一些啟發(fā)写隶,歡迎交流和Star。
1 JsonDeserializer介紹
JsonDeserializer 是一個接口讲仰,使用的時候需要實現(xiàn)這個接口并在 GsonBuilder 中對具體的類型去注冊慕趴。當反序列化到對應的類的時候就會調用這個自定義 JsonDeserializer 的 deserialize() 方法。下面對這個方法的幾個參數(shù)做一下解釋鄙陡,以便于更好的理解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 中代表一個數(shù)組類型,一個數(shù)組就是JsonElement的集合,這個集合中每一個類型都可能不同蹋宦。這是一個有序的集合披粟,意味著元素的添加順序是被維持著的。上面例子中l(wèi)ist對應的 “[]” 包裹起來的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 也實現(xiàn)了 Type 接口。
一般來講调塌,調用 GsonBuilder 的 registerTypeAdapter() 去注冊晋南,第一個參數(shù)使用 Class 類型就可以了。
1.3 JsonDeserializationContext
這個類是在反序列過程中羔砾,由其它類調用我們自定義的 JsonDeserialization 的 deserialize() 方法時傳遞過來的负间,在 Gson 中它唯一的一個實現(xiàn)是TreeTypeAdapter 中的一個私有的內部類 GsonContextImpl 〗啵可以在自定義的 JsonDeserializer 的 deserialize() 中去調用 JsonDeserializationContext 的 deserialize() 方法去獲得一個對象政溃。
但是要記住,如果傳遞到 JsonDeserializationContext 中的 json 與 JsonDeserializer 中的 json 一樣的話檀葛,可能會導致死循環(huán)調用。
2 思路分析
2.1 創(chuàng)建JavaBean
還是以最上面的那個 json 進行分析腹缩,在 list 對應 JsonArray 屿聋,其中的兩個 JsonObject 中,attributes 對應的 JsonObject 字段完全不一樣藏鹊,但是為了統(tǒng)一润讥,在寫 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 去注冊楚殿,當具體解析的時候我們會根據(jù)
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 作為參數(shù)回調自定義的 JsonDeserializer 规伐。我們就可以在下面這個方法中寫自己的邏輯得到我們需要的 Attribute 對象了。
public Attribute deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
但是細心的朋友應該會發(fā)現(xiàn)了匣缘,這個時候傳遞的 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胁镐。這樣就可以根據(jù) value 是 “address” 或者 “name” 去對 AddresAttribute 或者 NameAttribute 進行反序列化了偎血。
2.3 有一個坑
前面那我們講過,調用 JsonDeserializationContext 的方法應該注意死循環(huán)盯漂。在具體的實踐中颇玷,我雖然沒有調用 JsonDeserializationContext 的方法,但是依然出現(xiàn)了死循環(huán)的情況就缆。就是因為我是這么用的帖渠。
AttributeWithType attributeWithType = gson.fromJson(json, AttributeWithType.class);
乍一看沒什么問題啊,問題就出在這個 gson 身上竭宰。這個 gson 是已經(jīng)注冊過解析 AttributeWithType 的 GsonBuilder 創(chuàng)建的空郊。 gson.fromJson() 方法中的 json 是 AttributeWithType 對應的反序列化的 json,gson.fromJson() 內部會再次調用 AttributeWithType 對應的 JsonDeserializer 中的 deserialize() 方法切揭,從而導致死循環(huán)狞甚。
避免死循環(huán)的方式就是用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 中已經(jīng)含有能夠表示自身類型的字段桦踊,不需要注冊外層 Type,這樣更省事
// .registerTargetUpperLevelClass(AttributeWithType.class)
.registerTypeElementValueWithClassType("address", AddressAttribute.class)
.registerTypeElementValueWithClassType("name", NameAttribute.class)
.build();
ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);
2.如果在解析過程中發(fā)現(xiàn)有些類型沒有注冊到 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 結語
對于如何優(yōu)雅的解析這種類型不同的 JsonObject 绿聘,剛開始我是缺少思路的,在網(wǎng)上也沒有查到合適的文檔次舌。但是通過查看 Gson 的文檔和源碼熄攘,通過自己的理解和分析,逐步的完成了這個過程彼念。我的一個感觸就是挪圾,多去看看官方的使用文檔應該比盲目去搜索解決方案更好。
代碼是最好的文檔逐沙,本文只簡單介紹了一些實現(xiàn)思路哲思,文中貼出的一些代碼是為了講述方便,與項目中的代碼可能會有有些區(qū)別吩案。具體的使用可以看項目中的例子棚赔。
如果有問題,歡迎提 issue 或留言徘郭,如果對您有所幫助靠益,歡迎Star。