Gson 優雅實現多個枚舉的自定義(反)序列化過程

版權聲明:本文爲博主原創文章,未經博主容許不得轉載。 https://blog.csdn.net/RekaDowney/article/details/52292567
JDK版本

JDK版本.jpg

Gson版本
<dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.7</version>
    </dependency>
 

  Gson是實現對象序列化和反序列化的利器,可是Gson在(反)序列化枚舉時默認是根據枚舉值的名稱來實現的,若是你想要在(反)序列化枚舉時輸出本身定義的枚舉屬性,那麼此時至少有兩種選擇:java

  • 繼承TypeAdapter抽象類並實現其中的抽象方法
  • 實現JsonSerializer 和/或JsonDeserializer 接口

我一般是選擇第二種方式,即實現接口的方式來自定義(反)序列化過程。好比下面這個實例:
針對JDK1.8新的時間API中的LocalDate進行自定義(反)序列化過程:git

import com.google.gson.*;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateTypeAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
        return null == src ? null : new JsonPrimitive(formatter.format(src));
    }

    @Override
    public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return null == json ? null : LocalDate.parse(json.getAsString(), formatter);
    }

}
  當註冊了該 TypeAdapterGson在序列化 LocalDate類型時,會調用其中的 serialize方法,此時若是 LocalDate實例非空的話,咱們將調用 formatter.format(src)方法將 LocalDate實例格式化成指定形式的字符串,而後封裝成 JsonPrimitive實例並返回 GsonGson再將返回的 JsonPrimitive寫入到 json 字符串中;

  同理,當Gson反序列化時遇到LocalDate類型時,會調用其中的deserialize方法完成反序列化。github


  好了,扯完基本實現以後,如今有一個問題:那就是針對單個枚舉使用這種方式那是無可厚非的。可是當你的項目中應用到的枚舉數量比較多的時候,若是依然採用這種方式,那麼隨着而來的問題是類的增長以及出現一些小範圍的重複代碼。
  要如何解決這個問題就是寫做本文的意圖了。
  本文是基於JsonSerializerJsonDeserializer 接口來解決問題的,固然網上有針對TypeAdapter的工廠類TypeAdapterFactory來解決該問題,可是該解決方案有個不足之處,那就是須要修改Gson的源碼,這裏就不講該方案了,有須要的同窗自行搜索。
  第一步:一個接口,該接口定義了Gson在(反)序列化枚舉時將調用的方法:json

public interface GsonEnum<E> {

    String serialize();

    E deserialize(String jsonEnum);

}
 

 接口中定義接收一個泛型E,該泛型用來表示枚舉類型,裏面有兩個抽象方法,String serialize()方法表示將枚舉序列化成字符串,E deserialize(String jsonEnum)表示將字符串反序列化成枚舉並返回特定的枚舉Eide


  第二步:定義枚舉類並實現第一步中聲明的接口,這裏爲了演示效果,定義了以下兩個枚舉:
  派別枚舉Faction測試

public enum Faction implements GsonEnum<Faction> {

    ABNEGATION("無私派"), AMITY("和平派"), DAUNTLESS("無畏派"), CANDOR("誠實派"), ERUDITE("博學派");

    private final String name;

    Faction(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static Faction parse(String faction) {
        switch (faction) {
            case "無私派":
                return Faction.ABNEGATION;
            case "和平派":
                return Faction.AMITY;
            case "無畏派":
                return Faction.DAUNTLESS;
            case "誠實派":
                return Faction.CANDOR;
            case "博學派":
                return Faction.ERUDITE;
            default:
                throw new IllegalArgumentException("There is not enum names with [" + faction + "] of type Faction exists! ");
        }
    }

    @Override
    public Faction deserialize(String jsonEnum) {
        return Faction.parse(jsonEnum);
    }

    @Override
    public String serialize() {
        return this.getName();
    }

}
 

  性別枚舉Genderui

public enum Gender implements GsonEnum<Gender> {

    MALE("男"), FEMALE("女");

    private final String type;

    Gender(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public static Gender parse(String type) {
        switch (type) {
            case "男":
                return Gender.MALE;
            case "女":
                return Gender.FEMALE;
            default:
                throw new IllegalArgumentException("There is not enum names with [" + type + "] of type Gender exists! ");
        }
    }

    @Override
    public Gender deserialize(String jsonEnum) {
        return Gender.parse(jsonEnum);
    }

    @Override
    public String serialize() {
        return this.getType();
    }

}
 

  對於這兩個枚舉內部對GsonEnum接口的實現方法比較簡單,因此這裏就不解釋了。this


  第三步:自定義Gson(反)序列化過程的GsonEnumTypeAdapter,先看具體代碼實現:google

public class GsonEnumTypeAdapter<E> implements JsonSerializer<E>, JsonDeserializer<E> {

    private final GsonEnum<E> gsonEnum;

    public GsonEnumTypeAdapter(GsonEnum<E> gsonEnum) {
        this.gsonEnum = gsonEnum;
    }

    @Override
    public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) {
        if (null != src && src instanceof GsonEnum) {
            return new JsonPrimitive(((GsonEnum) src).serialize());
        }
        return null;
    }

    @Override
    public E deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (null != json) {
            return gsonEnum.deserialize(json.getAsString());
        }
        return null;
    }

}
 

  代碼看起來很簡單,惟一的構造器要求傳入一個GsonEnum的實現類。
  首先看一個測試用例:
  (反)序列化測試時用到的類Person.net

public class Person implements Serializable {

    private String name;
    private Gender gender;
    private Faction faction;
    private LocalDate birthday;

    public Person() {
    }

    public Person(String name, Gender gender, Faction faction, LocalDate birthday) {
        this.name = name;
        this.gender = gender;
        this.faction = faction;
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", gender=" + gender +
                ", faction=" + faction +
                ", birthday=" + birthday +
                '}';
    }

    // 省略 getter 和 setter
}
 

  JUnit測試實例

@Test
    public void testGson() throws Exception {
        Gson gson = new GsonBuilder()
                .serializeNulls()
                .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter())
                .registerTypeAdapter(Gender.class, new GsonEnumTypeAdapter<>(Gender.FEMALE))
                .registerTypeAdapter(Faction.class, new GsonEnumTypeAdapter<>(Faction.ABNEGATION))
                .create();

        Person p1 = new Person("雷卡", Gender.MALE, Faction.DAUNTLESS, LocalDate.of(1994, 10, 11));
        System.out.println("調用 toString 方法:\n" + p1);
        String jsonText = gson.toJson(p1);
        System.out.println("將 person 轉換成 json 字符串:\n" + jsonText);

        System.out.println("-------------------");

        Person p2 = gson.fromJson(jsonText, Person.class);
        assert p2 != p1;
        System.out.println("根據 json 字符串生成 person 實例:\n" + p2);

    }
 

測試結果爲:
Gson多枚舉(反)序列化測試結果.jpg


  測試結果代表咱們想要的效果已經達到了,如今講一下GsonEnumTypeAdapter的實現原理。
  以Faction爲例,首先它接收E時咱們傳遞的是Faction枚舉的一個枚舉值Faction.ABNEGATION,這個枚舉值沒有任何限定,能夠是該枚舉類中的任意一個。
  序列化Faction類型時,Gson調用其中的serialize方法,注意此時咱們傳入的Faction.ABNEGATION並不發揮做用,咱們直接調用的是序列化時遇到的Faction實例(好比Faction.AMITYFaction.CANDOR)的serialize實例方法,那麼此時返回的必然是該枚舉實例的name屬性值;
  反序列化遇到Faction類型時,Gson調用其中的deserialize方法,此時咱們實際上操做的最開始傳入的枚舉類型Faction.ABNEGATIONdeserialize方法,注意此時該方法須要傳入一個字符串,恰好能夠用到JsonElement自身提供的getAsString方法。再看Faction類中對deserialize方法的實現,實際上該方法並不關注調用者是誰,它真正關心的傳入值是什麼,根據傳入的值,它能夠很快獲取獲得對應的枚舉或者拋出異常。


  可能上面表述得不是很清楚,但實際上都是一些基礎的知識,即泛型,接口,枚舉的特殊性的綜合應用,多看幾遍應該就懂了,另外最好打上幾個斷點,而後debug一遍,看整個流程是怎麼跑的,那樣基本就清晰了。
  另外,爲確保GsonEnumTypeAdapter只接收枚舉而且GsonEnum只被枚舉類所實現,能夠添加泛型參數的邊界,以下:
  GsonEnum的類聲明

public interface GsonEnum<E extends Enum<E>>
 

 

  GsonEnumTypeAdapter的類聲明

public class GsonEnumTypeAdapter<E extends Enum<E>> implements JsonSerializer<E>, JsonDeserializer<E>
 

  聲明瞭泛型上限爲Enum以後能夠確保在使用該泛型時只有枚舉類可以被處理。


原創聲明:
本文爲我的原創,若有錯誤與不妥,歡迎提出!
另外,未經許可,謝絕轉載。

源碼地址1:Git@OSC

源碼地址2:GitHub

 
編寫日期:2016-08-23
發佈日期:2016-08-23
相關文章
相關標籤/搜索