Gson序列化那些事

Android開發(fā)中會經(jīng)常解析json渐溶,使用retrofit框架常用gson解析json。Gson有一定的容錯機(jī)制或者自定轉(zhuǎn)換,如"1"可以轉(zhuǎn)換為int 1long 1 等等票罐,反之也一樣。如果服務(wù)端傳空字符串過來泞边,這時怎么辦(服務(wù)端數(shù)據(jù)不修改情況下该押,下同)?

結(jié)構(gòu)

服務(wù)端數(shù)據(jù)(create_time為時間秒值, 后文Data.getJson()返回的是下面的json字符串)

{
  "id": "14",
  "name": "test1",
  "create_time": "1548122663"
}

解析對象

public class GameData {
    public String id;
    public String name;
    public Date createTime;
    
    @Override
    public String toString() {
        return "{id='" + id + "\', name='" + name + "\', createTime=" + createTime + '}';
    }
}

通常解析時不會填Date這種類型阵谚,因此可以改成String然后使用字段時進(jìn)行日期轉(zhuǎn)換蚕礼,這里假設(shè)解析后就是需要的Date對象烟具。如果用默認(rèn)的Gson解析將得到一個Json解析異常

// com.google.gson.JsonSyntaxException: 1548122663
GameData data = new Gson().fromJson(Data.getJson(), GameData.class);

JsonSerializer與JsonDeserializer

JsonDeserializer 表示自定義json序列化, JsonDeserializer 表示自定義json反序列化奠蹬,來看下它們怎么用朝聋。

自定義json反序列化

// 自定義 GameDat 反序列化
public class GameDataDeserializer implements JsonDeserializer<GameData> {
    @Override
    public GameData deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        GameData data = new GameData();
        if (jsonElement instanceof JsonObject) {
            JsonObject json = (JsonObject) jsonElement;
            data.id = json.get("id").getAsString();
            data.name = json.get("name").getAsString();
            data.createTime = new Date(json.get("create_time").getAsLong() * 1000);
        }
        return data;
    }
}

// 自定義 GameDat 反序列化使用
@Test
public void testCustomDeserializer() throws Exception {
    Gson gson = new GsonBuilder()
            // 注冊反序列化適配器
            .registerTypeAdapter(GameData.class, new GameDataDeserializer())
            .create();
    GameData data = gson.fromJson(Data.getJson(), GameData.class);
    System.out.println(data);//{id='14', name='test1', createTime=Tue Jan 22 10:04:23 CST 2019}
}

自定義序列化

//Gson自帶序列化的效果
@Test
public void testDefaultSerializer() throws Exception{
    GameData data = new GameData();
    data.id = "14";
    data.name = "testSerializer";
    data.createTime = new Date(1548122663L*1000);
    System.out.println(new Gson().toJson(data));
    //{"id":"14","name":"testSerializer","create_time":"Jan 22, 2019 10:04:23 AM"}
}

// 自定義 GameDate 序列化
public class GameDataSerializer implements JsonSerializer<GameData> {
    @Override
    public JsonElement serialize(GameData src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject json = new JsonObject();
        json.addProperty("id", src.id);
        json.addProperty("name", src.name);
        json.addProperty("create_time", String.valueOf(src.createTime.getTime() / 1000));
        return json;
    }
}

//使用自定義 GameDate 序列化
@Test
public void testCustomSerializer() throws Exception {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(GameData.class, new GameDataSerializer())
            .create();
    GameData data = new GameData();
    data.id = "14";
    data.name = "testSerializer";
    data.createTime = new Date(1548122663L * 1000);
    System.out.println(gson.toJson(data));
    //{"id":"14","name":"testSerializer","create_time":"1548122663"}
}

TypeAdapter

如果序列化和反序列化都需要,可以考慮使用 TypeAdapter

// 自定義 GameDataTypeAdapter
public class GameDataTypeAdapter extends TypeAdapter<GameData> {
    //序列化
    @Override
    public void write(JsonWriter out, GameData data) throws IOException {
        out.beginObject();
        out.name("id").value(data.id)
                .name("name").value(data.name)
                .name("create_time").value(String.valueOf(data.createTime.getTime() / 1000));
        out.endObject();
    }

    // 反序列化
    @Override
    public GameData read(JsonReader in) throws IOException {
        in.beginObject();
        GameData data = new GameData();
        while (in.peek() != JsonToken.END_OBJECT) {
            String name = in.nextName();
            if ("id".equals(name)) {
                data.id = in.nextString();
            } else if ("name".equals(name)) {
                data.name = in.nextString();
            } else if ("create_time".equals(name)) {
                data.createTime = new Date(in.nextLong() * 1000);
            }
        }
        in.endObject();
        return data;
    }
}

@Test
public void testTypeAdapter() throws Exception {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(GameData.class, new GameDataTypeAdapter())
            .create();
    GameData data = gson.fromJson(Data.getJson(), GameData.class);
    System.out.println(data);
    System.out.println(gson.toJson(data));
    //{id='14', name='test1', createTime=Tue Jan 22 10:04:23 CST 2019}
    //{"id":"14","name":"test1","create_time":"1548122663"}
}

TypeAdapterFactory

如果要為一系列的適配器進(jìn)行注冊, 自定義TypeAdapterFactory比較有用

class GameDataTypeFactory implements TypeAdapterFactory{
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if(type.getRawType() == GameData.class){
            return (TypeAdapter<T>) new GameDataTypeAdapter();
        }
        return null;
    }
}

@Test
public void testTypeAdapterFactory() throws Exception {
    Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(new GameDataTypeFactory())
            .create();
    GameData data = gson.fromJson(Data.getJson(), GameData.class);
    System.out.println(data);
    System.out.println(gson.toJson(data));
    //{id='14', name='test1', createTime=Tue Jan 22 10:04:23 CST 2019}
    //{"id":"14","name":"test1","create_time":"1548122663"}
}

registerAdapter

JsonSerializer<Number> numberSerializer = new JsonSerializer<Number>() {
    @Override
    public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive("cn:"+String.valueOf(src));
    }
};

//希望通過 registerTypeAdapter 注冊父類的序列化來實現(xiàn)子類的序列化
@Test
public void testRegisterTypeAdapterByBaseClass() throws Exception {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Number.class, numberSerializer)
            .create();
    System.out.println(gson.toJson(100));//100
}

// 只好通過子類一個一個的注冊了
@Test
public void testRegisterTypeAdapterEachSubclass() throws Exception {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Integer.class, numberSerializer)
            .registerTypeAdapter(Long.class, numberSerializer)
            .registerTypeAdapter(Float.class, numberSerializer)
            .registerTypeAdapter(Double.class, numberSerializer)
            .create();
    System.out.println(gson.toJson(100));//cn:100
}

上面說明通過registerTypeAdapter注冊父類的序列化來實現(xiàn)子類的序列化是行不通的,需要使用 registerTypeHierarchyAdapter

@Test
public void testRegisterTypeHierarchyAdapter() throws Exception {
    Gson gson = new GsonBuilder()
            .registerTypeHierarchyAdapter(Number.class, numberSerializer)
            .create();
    System.out.println(gson.toJson(100));//cn:100
}

@JsonAdapter注解

上面的TypeAdapter,JsonDeserializerJsonSerializer的使用都需要通過GsonBuilderregisterTypeAdapterregisterTypeHierarchyAdapter進(jìn)行注冊,需要配置Gson. @JsonAdapter注解正是為了無縫注冊而生

@JsonAdapter(GameDataDeserializer.class)
public class GameData {
    ...
}
GameData data = new Gson().fromJson(Data.getJson(), GameData.class);

一個類默認(rèn)只能用一個@JsonAdapter, @JsonAdapter注解支持字段

public class SomeEntity {
    @JsonAdapter(GameDataDeserializer2.class)
    GameData data;
}

應(yīng)用

字段轉(zhuǎn)換

{
  "xxx": "yyy",
  "showItem": "0" // 0-顯示, 1 不顯示
}

如上的json, 通常定義兩個字符串字段即可, 使用時根據(jù)showItem來決定是否顯示:

  if("0".equals(showItem) {
    ...
  }else {
    ...
  }

每次這樣處理總覺得啰嗦, 即使抽取常量也是不治本(還得標(biāo)明和哪些常量比較), 既然是表示boolean的量,用boolean類型最好了, 在不要求服務(wù)端做修改的情況下, 可以使用TypeAdapter在String和boolean間互相轉(zhuǎn)換:

/**
 * [Boolean]與[String]轉(zhuǎn)換
 * ```
 * string      boolean
 *  "0"   ->    false
 *  "1"   ->    true
 * ```
 */
class BooleanAdapter : TypeAdapter<Boolean>() {
    override fun write(out: JsonWriter, value: Boolean?) {
        out.value(if (value == true) 1 else 0)
    }

    override fun read(`in`: JsonReader): Boolean {
       if (`in`.peek() == JsonToken.NULL) {
             `in`.nextNull()
             return false
       }
        return try {
            val nextString = `in`.nextString()
            !nextString.isNullOrBlank() && "0" != nextString
        } catch (e: Exception) {
            false
        }
    }
}

注: 項目中使用的kotlin版本直接粘貼了, java類似. 另外這里showItem并不是一個完整的json, 因此轉(zhuǎn)換時不需要 beginObjectbeginArray之類, 是單純的字段轉(zhuǎn)換.

這樣就可以將showItem定義成boolean類型了:

  @JsonAdapter(BooleanAdapter::class)
  @SerializedName("showItem")
  var showItem: Boolean = false

Enum

還有一個比較實用的場景就是枚舉的轉(zhuǎn)換了. 服務(wù)端設(shè)定了類型, 客戶端解析轉(zhuǎn)換成枚舉對象

  "type":"0" //0-無跳轉(zhuǎn) 1-動畫 2-漫畫 3-文章 4-專題 5-外鏈 6-評論 7-分類, 也是單純字段轉(zhuǎn)換

創(chuàng)建對應(yīng)枚舉類和TypeAdapter(不用序列化也可以只用JsonDeserializer), 這樣使用時就不需要和一堆012比較了,也省了注解替換枚舉的必要.

總結(jié)

  • 自定義(反)序列化可以使用TypeAdapter,TypeAdapterFactory,JsonDeserializerJsonSerializer
  • 自定義(反)序列化支持@JsonAdapter注解, @JsonAdapter支持字段
  • 相信你已經(jīng)知道如何解決開頭提到的空字符串和不規(guī)則json之類的問題了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囤躁,一起剝皮案震驚了整個濱河市冀痕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌割以,老刑警劉巖金度,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異严沥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)中姜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門消玄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丢胚,你說我怎么就攤上這事翩瓜。” “怎么了携龟?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵兔跌,是天一觀的道長。 經(jīng)常有香客問我峡蟋,道長坟桅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任蕊蝗,我火速辦了婚禮仅乓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蓬戚。我一直安慰自己夸楣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布子漩。 她就那樣靜靜地躺著豫喧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幢泼。 梳的紋絲不亂的頭發(fā)上紧显,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音旭绒,去河邊找鬼鸟妙。 笑死焦人,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的重父。 我是一名探鬼主播花椭,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼房午!你這毒婦竟也來了矿辽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤郭厌,失蹤者是張志新(化名)和其女友劉穎袋倔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體折柠,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡宾娜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了扇售。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片前塔。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖承冰,靈堂內(nèi)的尸體忽然破棺而出华弓,到底是詐尸還是另有隱情,我是刑警寧澤困乒,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布寂屏,位于F島的核電站,受9級特大地震影響娜搂,放射性物質(zhì)發(fā)生泄漏迁霎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一涌攻、第九天 我趴在偏房一處隱蔽的房頂上張望欧引。 院中可真熱鬧,春花似錦恳谎、人聲如沸芝此。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婚苹。三九已至,卻和暖如春鸵膏,著一層夾襖步出監(jiān)牢的瞬間膊升,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工谭企, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留廓译,地道東北人评肆。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像非区,于是被迫代替她去往敵國和親瓜挽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

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

  • 1.概述2.Gson的目標(biāo)3.Gson的性能和擴(kuò)展性4.Gson的使用者5.如何使用Gson 通過Maven來使用...
    人失格閱讀 14,236評論 2 18
  • 概述 Moshi是Square公司在2015年6月開源的有關(guān)Json的反序列化及序列化的框架征绸,說到Json久橙,大家應(yīng)...
    wustor閱讀 12,791評論 7 33
  • 前言 之前一段時間,準(zhǔn)備把糗百的項目中json解析的模塊中的原生Json解析換成gson解析管怠,工作比較繁雜淆衷,坑多,...
    拉丁吳閱讀 3,170評論 9 29
  • 為了更好的學(xué)習(xí)Gson渤弛,特將Gson User Guide翻譯如下祝拯。由于本人英文水平有限,如有錯誤她肯,還請指正鹿驼,謝謝...
    WeberLisper閱讀 6,820評論 0 6
  • 概況 Gson是一個Java庫,它可以用來把Java對象轉(zhuǎn)換為JSON表達(dá)式辕宏,也可以反過來把JSON字符串轉(zhuǎn)換成與...
    木豚閱讀 6,795評論 0 2