Gson,不規范json的反序列化

??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;
}
  1. 在需要反序列化的類或字段上直接使用@JsonAdapter(value = IllegalJsonDeserializer.class)注解
  2. 如果需要獲取其他層級的值,可在字段上聲明@Select(value = "address.city"),其中value的值用.作為分隔符作為不同層級的劃分,比如上面的值就是說明要將address下的city字段賦值到Content類中的city字段

??除此之外,該反序列方法還兼容了Gson本身的注解,完全可以使用Gson的原生注解方法。源碼已放在github上,如果有遇到同樣問題或者對此感興趣朋友的可以看一下。

https://github.com/chengzhicao/illegal-json

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容

  • 1.概述2.Gson的目標3.Gson的性能和擴展性4.Gson的使用者5.如何使用Gson 通過Maven來使用...
    人失格閱讀 14,303評論 2 18
  • 為了更好的學習Gson,特將Gson User Guide翻譯如下。由于本人英文水平有限,如有錯誤,還請指正,謝謝...
    WeberLisper閱讀 6,873評論 0 6
  • 概述 Moshi是Square公司在2015年6月開源的有關Json的反序列化及序列化的框架,說到Json,大家應...
    wustor閱讀 12,841評論 7 33
  • 好久不記得夢了 今早又做夢 被夢嚇醒 喝了口溫水睡意全無 不敢奢望更多 只求不再夢見 我不好 對不起不祝你好
    夢夢夢夢happy閱讀 174評論 0 0
  • 夜晚下起了大雨: 南宮雨軒被雷聲吵醒,這時雨軒想起自己的手鏈忘在海岸懸崖邊,雨軒走進南宮雨夜的房間,雨...
    檸檬哆蜜棗閱讀 238評論 0 0