??Gson是Java或Android開(kāi)發(fā)中常用的一個(gè)json解析庫(kù)录别,尤其是在Android下基本上是必備的。但是做項(xiàng)目嘛邻吞,總會(huì)遇到各種奇葩的問(wèn)題组题,這里所說(shuō)的不規(guī)范json并不是說(shuō)json格式不規(guī)范,畢竟格式都不規(guī)范的話就談不上是個(gè)json串了抱冷,是不可能解析的崔列。這里所謂的不規(guī)范json是在后臺(tái)反回的json串能解析的情況下,我們的實(shí)體類(lèi)不能很方便的接收它旺遮,看下面的情況(由此可見(jiàn)后臺(tái)數(shù)據(jù)格式對(duì)前端的影響之大赵讯,有時(shí)候?qū)笈_(tái)友好的數(shù)據(jù)格式對(duì)前端來(lái)說(shuō)并不友好反而會(huì)增加前端的工作量,尤其是像Java這種強(qiáng)類(lèi)型語(yǔ)言來(lái)說(shuō)趣效,要想用的方便那就每一個(gè)字段都要嚴(yán)格限制它的數(shù)據(jù)類(lèi)型
):
[
{
"id": 1097320752316833800,
"newsInfo": {
"geolocation": {
"lon": "0",
"lat": "0"
},
"type": "5",
"id": "1097320752316833794",
"contentId": 1097320752316833800,
"publishTime": "2019-02-18 10:23:49"
},
"freshnewsInfo": "",
"activityContent": "",
"specialInfo": "",
"eventInfo": "",
"contentType": "1"
},
{
"id": 1097320752316833800,
"newsInfo": "",
"freshnewsInfo": {
"sourceType": 2,
"videoImgUrl": "",
"name": "用戶(hù)TrrLXe",
"relatednewsId": "",
"fileId": "",
"imeiNo": "",
"createTime": "2019-02-18 09:44:04",
"geolocation": {
"lon": 116.4835,
"lat": 39.9235
}
},
"activityContent": "",
"specialInfo": "",
"eventInfo": "",
"contentType": "1",
"sequence": "",
"createTime": "2019-02-18 10:56:25",
"showType": 1
}
]
public class Content{
private long id;
private NewsInfoBean newsInfo;
private FreshnewsInfoBean freshnewsInfo;
private String activityContent;
private EventInfoBean eventInfo;
private SpecialInfoBean specialInfo;
private int contentType;
private String sequence;
private String createTime;
private String showType;
}
??上面的json串相當(dāng)于一個(gè)List瘦癌,每個(gè)Content中都有若干個(gè)*Info(newsInfo、SpecialInfo跷敬、eventInfo等)字段讯私,其中只有一個(gè)*Info字段有值,其余沒(méi)有值的理論上應(yīng)該為null西傀,但是后臺(tái)返回的是空字符串斤寇,這樣問(wèn)題就大了,當(dāng)為空字符串的時(shí)候怎么解析呢拥褂?空字符串是無(wú)法賦到一個(gè)實(shí)體對(duì)象的娘锁,對(duì)于Gson來(lái)說(shuō)會(huì)報(bào)下面這樣一個(gè)錯(cuò)誤:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 26 path $.data[0].newsInfo
??意思是說(shuō)要解析的newsInfo值應(yīng)該是個(gè)Object類(lèi)型,但現(xiàn)在卻是一個(gè)String類(lèi)型饺鹃,所以解析失敗了莫秆。好吧,這個(gè)問(wèn)題跟后臺(tái)人員反應(yīng)讓他們把空字符串轉(zhuǎn)為null悔详,但他們也很無(wú)奈镊屎,反復(fù)溝通后無(wú)果,最終還是把問(wèn)題拋給了前端/(ㄒoㄒ)/~~茄螃。
??其實(shí)這個(gè)問(wèn)題還是有不少解決方式的缝驳,比如改下實(shí)體結(jié)構(gòu):
public class Content{
private long id;
private Object newsInfo;
private Object freshnewsInfo;
private String activityContent;
private Object eventInfo;
private Object specialInfo;
private int contentType;
private String sequence;
private String createTime;
private String showType;
}
??將所有不能正常解析的字段都改用Object類(lèi)型接收,然后再根據(jù)需要解析Object到具體的實(shí)體結(jié)構(gòu);或者是使用Gson框架中的JsonParse類(lèi)解析返回的json數(shù)據(jù)用狱,這兩種方式都可以运怖,但對(duì)于整個(gè)項(xiàng)目來(lái)說(shuō),有不少接口是這種不規(guī)范的格式夏伊,每個(gè)都這樣做的話無(wú)疑增加了成本和復(fù)雜度摇展,此外還有一個(gè)更令人抓狂的問(wèn)題,而且我相信很多人都碰到過(guò)署海。
??還是上面的json串吗购,在newsInfo和refreshInfo字段中医男,有個(gè)geolocation字段:
"newsInfo": {
"geolocation": {
"lon": "0",
"lat": "0"
},
"type": "5",
"id": "1097320752316833794",
"contentId": 1097320752316833800,
"publishTime": "2019-02-18 10:23:49"
}
??對(duì)于后臺(tái)來(lái)說(shuō)砸狞,從不同的表中查出的字段放到一個(gè)單獨(dú)的對(duì)象實(shí)體中沒(méi)問(wèn)題,但對(duì)于前端來(lái)說(shuō)镀梭,放到和其他內(nèi)容一個(gè)級(jí)別下才是最方便的刀森,比如我們期望它的解析實(shí)體是長(zhǎng)這樣的:
public class NewsInfo {
private double lon;
private double lat;
private int type;
private long id;
private long contentId;
private String publishTime;
}
??如果這個(gè)json串中還有地址信息、作者信息話那有可能會(huì)是這樣的:
"newsInfo": {
"geolocation": {
"lon": "0",
"lat": "0"
},
"type": "5",
"id": "1097320752316833794",
"contentId": 1097320752316833800,
"publishTime": "2019-02-18 10:23:49"
"address": {
"country":"CN",
"countryNO":"86",
"city":"北京"
},
"author": {
"nickName":"xiaxia",
"icon":"",
"registTime":"2018-12-18 09:33:50",
"type":"1",
}
}
??而我需要的只是address中的city或是author中的nickName和icon字段报账,所以我們期望它的解析實(shí)體是這樣:
public class NewsInfo {
private double lon;
private double lat;
private String city;
private String nickName;
private String icon;
private int type;
private long id;
private long contentId;
private String publishTime;
}
??這種實(shí)體結(jié)構(gòu)研底,使用Gson是無(wú)法正常解析上面的json串的,但Gson框架提供了強(qiáng)大的可擴(kuò)展功能透罢,我們完全可以自定義解析方式來(lái)達(dá)到我們的需求榜晦。
Gson序列化和反序列化的方式
??自定義json解析,Gson框架為我們提供了以下幾種方式:
- 繼承TypeAdapter類(lèi)
??需要重寫(xiě)read(JsonReader in)反序列化方法和write(JsonWriter out, Object value)序列化方法羽圃,擴(kuò)展性低乾胶,只能針對(duì)一種類(lèi)型進(jìn)行處理。
- 實(shí)現(xiàn)TypeAdapterFactory接口
??需要重寫(xiě)create(Gson gson, TypeToken<T> type)方法朽寞,并返回一個(gè)TypeAdapter對(duì)象识窿,擴(kuò)展性高,可通過(guò)判斷類(lèi)型來(lái)創(chuàng)建對(duì)應(yīng)的TypeAdapter
- 實(shí)現(xiàn)JsonDeserializer或JsonSerializer接口
??擴(kuò)展性高脑融,將序列化和反序列化分開(kāi)喻频,可只針對(duì)其中一種進(jìn)行自定義
??這3種方法本質(zhì)上都是創(chuàng)建一個(gè)新的TypeAdapter,其中前兩種需要重寫(xiě)序列化和反序列化方法肘迎,第三種是將序列化和反序列化方法分開(kāi)了甥温,可以單獨(dú)實(shí)現(xiàn)一種,我們這里使用第三種方式妓布,實(shí)現(xiàn)JsonDeserializer接口姻蚓,這樣的話我們就只自定義了反序列化方法,序列化方法仍然走Gson框架本身秋茫。
Gson反序列化過(guò)程
??在Gson框架中史简,反序列化的過(guò)程是這樣的:
1. 解析json串后得到一個(gè)JsonReader;
2. 通過(guò)hasNext()迭代JsonReader獲取當(dāng)前json串的位置;
3. 再通過(guò)peek()方法判斷該位置下的類(lèi)型(BEGIN_OBJECT圆兵、BEGIN_ARRAY跺讯、END_ARRAY、END_OBJECT殉农、NAME等刀脏,具體可看JsonToken類(lèi));
4. 如果該類(lèi)型是個(gè)NAME就說(shuō)明遇到一個(gè)字段,那么通過(guò)nextName()方法獲取該字段的值超凳,然后執(zhí)行第5步驟愈污,否則繼續(xù)執(zhí)行第3步驟;
5. 如果實(shí)體對(duì)象中有該字段轮傍,那再獲取該字段的數(shù)據(jù)類(lèi)型暂雹;
6. 通過(guò)判斷該數(shù)據(jù)類(lèi)型來(lái)決定接下將會(huì)通過(guò)JsonReader的哪個(gè)方法(nextString()、beginObject()创夜、beginArray)獲取到字段的值杭跪。
??以上就是Gson中反序列化的過(guò)程,我們上面發(fā)生Gson報(bào)錯(cuò)的地方就是在第6步發(fā)生的驰吓,由于newsInfo字段是個(gè)Object涧尿,但在JsonReader中卻是一個(gè)String,所以對(duì)于一個(gè)String類(lèi)型來(lái)說(shuō)如果執(zhí)行beginObject()方法就會(huì)報(bào)錯(cuò)檬贰。這里也是我不明白的地方姑廉,對(duì)于Gson框架來(lái)說(shuō)為什么不通過(guò)peek()方法先判斷一下類(lèi)型進(jìn)行容錯(cuò)然后再獲取值呢。
自定義反序列化過(guò)程
??Gson的反序列化過(guò)程是順序執(zhí)行的翁涤,從json串的頭開(kāi)始一直迭代到尾遇到什么就獲取什么桥言,可謂是行云流水一氣呵成。接下來(lái)針對(duì)我們的需求重新定義一下反序列化過(guò)程迷雪,主要有三個(gè)目標(biāo):
1.擺脫后臺(tái)數(shù)據(jù)格式對(duì)前端的影響限书,前端實(shí)體類(lèi)不一定完全按照后臺(tái)的格式寫(xiě)
2.增加json反序列化的容錯(cuò)能力
3.獲取json中不同層級(jí)的值
1. 實(shí)現(xiàn)JsonDeserializer接口,并重寫(xiě)其中方法章咧;
2. 獲取實(shí)體類(lèi)的所有字段倦西,并獲取字段的注解信息;
3. 獲取JsonElement中的members字段赁严,這是一個(gè)LinkedTreeMap類(lèi)型扰柠,存儲(chǔ)了解析json后的層級(jí)結(jié)構(gòu);
4. 遍歷字段疼约,獲取members中對(duì)應(yīng)字段的值卤档,該值也是一個(gè)JsonElement類(lèi)型;
5. 判斷字段類(lèi)型和該JsonElement類(lèi)型是否一致程剥,避免類(lèi)型不同而在賦值時(shí)報(bào)錯(cuò)劝枣;
6. 如果該類(lèi)型是基本類(lèi)型則進(jìn)行賦值操作,否則該類(lèi)型可能是數(shù)組或?qū)嶓w類(lèi),那就重復(fù)執(zhí)行第四步驟舔腾;
??很顯然溪胶,該反序列化過(guò)程不是順序執(zhí)行的,而是根據(jù)json的層次結(jié)構(gòu)進(jìn)行查找的稳诚,雖然速度上落后了但靈活性卻提高了哗脖,可以根據(jù)實(shí)體字段隨意查找想要的值。
使用方式
@JsonAdapter(value = IllegalJsonDeserializer.class)
public class Content{
private long id;
@Select(value = "address.city")
private String city;
private NewsInfoBean newsInfo;
private FreshnewsInfoBean freshnewsInfo;
private String activityContent;
private EventInfoBean eventInfo;
private SpecialInfoBean specialInfo;
private int contentType;
private String sequence;
private String createTime;
private String showType;
}
- 在需要反序列化的類(lèi)或字段上直接使用
@JsonAdapter(value = IllegalJsonDeserializer.class)
注解 - 如果需要獲取其他層級(jí)的值扳还,可在字段上聲明
@Select(value = "address.city")
才避,其中value的值用.
作為分隔符作為不同層級(jí)的劃分,比如上面的值就是說(shuō)明要將address下的city字段賦值到Content類(lèi)中的city字段
??除此之外氨距,該反序列方法還兼容了Gson本身的注解桑逝,完全可以使用Gson的原生注解方法。源碼已放在github上衔蹲,如果有遇到同樣問(wèn)題或者對(duì)此感興趣朋友的可以看一下肢娘。