??Gson是Java或Android開發中常用的一個json解析庫,尤其是在Android下基本上是必備的。但是做項目嘛,總會遇到各種奇葩的問題,這里所說的不規范json并不是說json格式不規范,畢竟格式都不規范的話就談不上是個json串了,是不可能解析的。這里所謂的不規范json是在后臺反回的json串能解析的情況下,我們的實體類不能很方便的接收它,看下面的情況(由此可見后臺數據格式對前端的影響之大,有時候對后臺友好的數據格式對前端來說并不友好反而會增加前端的工作量,尤其是像Java這種強類型語言來說,要想用的方便那就每一個字段都要嚴格限制它的數據類型
):
[
{
"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": "用戶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串相當于一個List,每個Content中都有若干個*Info(newsInfo、SpecialInfo、eventInfo等)字段,其中只有一個*Info字段有值,其余沒有值的理論上應該為null,但是后臺返回的是空字符串,這樣問題就大了,當為空字符串的時候怎么解析呢?空字符串是無法賦到一個實體對象的,對于Gson來說會報下面這樣一個錯誤:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 26 path $.data[0].newsInfo
??意思是說要解析的newsInfo值應該是個Object類型,但現在卻是一個String類型,所以解析失敗了。好吧,這個問題跟后臺人員反應讓他們把空字符串轉為null,但他們也很無奈,反復溝通后無果,最終還是把問題拋給了前端/(ㄒoㄒ)/~~。
??其實這個問題還是有不少解決方式的,比如改下實體結構:
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類型接收,然后再根據需要解析Object到具體的實體結構;或者是使用Gson框架中的JsonParse類解析返回的json數據,這兩種方式都可以,但對于整個項目來說,有不少接口是這種不規范的格式,每個都這樣做的話無疑增加了成本和復雜度,此外還有一個更令人抓狂的問題,而且我相信很多人都碰到過。
??還是上面的json串,在newsInfo和refreshInfo字段中,有個geolocation字段:
"newsInfo": {
"geolocation": {
"lon": "0",
"lat": "0"
},
"type": "5",
"id": "1097320752316833794",
"contentId": 1097320752316833800,
"publishTime": "2019-02-18 10:23:49"
}
??對于后臺來說,從不同的表中查出的字段放到一個單獨的對象實體中沒問題,但對于前端來說,放到和其他內容一個級別下才是最方便的,比如我們期望它的解析實體是長這樣的:
public class NewsInfo {
private double lon;
private double lat;
private int type;
private long id;
private long contentId;
private String publishTime;
}
??如果這個json串中還有地址信息、作者信息話那有可能會是這樣的:
"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字段,所以我們期望它的解析實體是這樣:
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;
}
??這種實體結構,使用Gson是無法正常解析上面的json串的,但Gson框架提供了強大的可擴展功能,我們完全可以自定義解析方式來達到我們的需求。
Gson序列化和反序列化的方式
??自定義json解析,Gson框架為我們提供了以下幾種方式:
- 繼承TypeAdapter類
??需要重寫read(JsonReader in)反序列化方法和write(JsonWriter out, Object value)序列化方法,擴展性低,只能針對一種類型進行處理。
- 實現TypeAdapterFactory接口
??需要重寫create(Gson gson, TypeToken<T> type)方法,并返回一個TypeAdapter對象,擴展性高,可通過判斷類型來創建對應的TypeAdapter
- 實現JsonDeserializer或JsonSerializer接口
??擴展性高,將序列化和反序列化分開,可只針對其中一種進行自定義
??這3種方法本質上都是創建一個新的TypeAdapter,其中前兩種需要重寫序列化和反序列化方法,第三種是將序列化和反序列化方法分開了,可以單獨實現一種,我們這里使用第三種方式,實現JsonDeserializer接口,這樣的話我們就只自定義了反序列化方法,序列化方法仍然走Gson框架本身。
Gson反序列化過程
??在Gson框架中,反序列化的過程是這樣的:
1. 解析json串后得到一個JsonReader;
2. 通過hasNext()迭代JsonReader獲取當前json串的位置;
3. 再通過peek()方法判斷該位置下的類型(BEGIN_OBJECT、BEGIN_ARRAY、END_ARRAY、END_OBJECT、NAME等,具體可看JsonToken類);
4. 如果該類型是個NAME就說明遇到一個字段,那么通過nextName()方法獲取該字段的值,然后執行第5步驟,否則繼續執行第3步驟;
5. 如果實體對象中有該字段,那再獲取該字段的數據類型;
6. 通過判斷該數據類型來決定接下將會通過JsonReader的哪個方法(nextString()、beginObject()、beginArray)獲取到字段的值。
??以上就是Gson中反序列化的過程,我們上面發生Gson報錯的地方就是在第6步發生的,由于newsInfo字段是個Object,但在JsonReader中卻是一個String,所以對于一個String類型來說如果執行beginObject()方法就會報錯。這里也是我不明白的地方,對于Gson框架來說為什么不通過peek()方法先判斷一下類型進行容錯然后再獲取值呢。
自定義反序列化過程
??Gson的反序列化過程是順序執行的,從json串的頭開始一直迭代到尾遇到什么就獲取什么,可謂是行云流水一氣呵成。接下來針對我們的需求重新定義一下反序列化過程,主要有三個目標:
1.擺脫后臺數據格式對前端的影響,前端實體類不一定完全按照后臺的格式寫
2.增加json反序列化的容錯能力
3.獲取json中不同層級的值
1. 實現JsonDeserializer接口,并重寫其中方法;
2. 獲取實體類的所有字段,并獲取字段的注解信息;
3. 獲取JsonElement中的members字段,這是一個LinkedTreeMap類型,存儲了解析json后的層級結構;
4. 遍歷字段,獲取members中對應字段的值,該值也是一個JsonElement類型;
5. 判斷字段類型和該JsonElement類型是否一致,避免類型不同而在賦值時報錯;
6. 如果該類型是基本類型則進行賦值操作,否則該類型可能是數組或實體類,那就重復執行第四步驟;
??很顯然,該反序列化過程不是順序執行的,而是根據json的層次結構進行查找的,雖然速度上落后了但靈活性卻提高了,可以根據實體字段隨意查找想要的值。
使用方式
@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;
}
- 在需要反序列化的類或字段上直接使用
@JsonAdapter(value = IllegalJsonDeserializer.class)
注解 - 如果需要獲取其他層級的值,可在字段上聲明
@Select(value = "address.city")
,其中value的值用.
作為分隔符作為不同層級的劃分,比如上面的值就是說明要將address下的city字段賦值到Content類中的city字段
??除此之外,該反序列方法還兼容了Gson本身的注解,完全可以使用Gson的原生注解方法。源碼已放在github上,如果有遇到同樣問題或者對此感興趣朋友的可以看一下。