Gson漓雅,不規(guī)范json的反序列化

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

??除此之外氨距,該反序列方法還兼容了Gson本身的注解桑逝,完全可以使用Gson的原生注解方法。源碼已放在github上衔蹲,如果有遇到同樣問(wèn)題或者對(duì)此感興趣朋友的可以看一下肢娘。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呈础,一起剝皮案震驚了整個(gè)濱河市舆驶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌而钞,老刑警劉巖沙廉,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異臼节,居然都是意外死亡撬陵,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)网缝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巨税,“玉大人,你說(shuō)我怎么就攤上這事粉臊〔萏恚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵扼仲,是天一觀的道長(zhǎng)远寸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)屠凶,這世上最難降的妖魔是什么驰后? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮矗愧,結(jié)果婚禮上灶芝,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好夜涕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布皂贩。 她就那樣靜靜地躺著钥勋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衡瓶,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音交排,去河邊找鬼乙帮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛俏拱,可吹牛的內(nèi)容都是我干的暑塑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锅必,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼事格!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起搞隐,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤驹愚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后劣纲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逢捺,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年癞季,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劫瞳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绷柒,死狀恐怖志于,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情废睦,我是刑警寧澤伺绽,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站郊楣,受9級(jí)特大地震影響憔恳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜净蚤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一钥组、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧今瀑,春花似錦程梦、人聲如沸点把。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)郎逃。三九已至,卻和暖如春挺份,著一層夾襖步出監(jiān)牢的瞬間褒翰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工匀泊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留优训,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓各聘,卻偏偏與公主長(zhǎng)得像揣非,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子躲因,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.概述2.Gson的目標(biāo)3.Gson的性能和擴(kuò)展性4.Gson的使用者5.如何使用Gson 通過(guò)Maven來(lái)使用...
    人失格閱讀 14,204評(píng)論 2 18
  • 為了更好的學(xué)習(xí)Gson早敬,特將Gson User Guide翻譯如下。由于本人英文水平有限大脉,如有錯(cuò)誤搞监,還請(qǐng)指正,謝謝...
    WeberLisper閱讀 6,713評(píng)論 0 6
  • 概述 Moshi是Square公司在2015年6月開(kāi)源的有關(guān)Json的反序列化及序列化的框架箱靴,說(shuō)到Json腺逛,大家應(yīng)...
    wustor閱讀 12,733評(píng)論 7 33
  • 好久不記得夢(mèng)了 今早又做夢(mèng) 被夢(mèng)嚇醒 喝了口溫水睡意全無(wú) 不敢奢望更多 只求不再夢(mèng)見(jiàn) 我不好 對(duì)不起不祝你好
    夢(mèng)夢(mèng)夢(mèng)夢(mèng)happy閱讀 146評(píng)論 0 0
  • 夜晚下起了大雨: 南宮雨軒被雷聲吵醒,這時(shí)雨軒想起自己的手鏈忘在海岸懸崖邊衡怀,雨軒走進(jìn)南宮雨夜的房間,雨...
    檸檬哆蜜棗閱讀 227評(píng)論 0 0