GSON定制序列化/反序列化

Gson支持泛型的反序列化:

@Test
public void simplestGeneric() throws Exception {
    String serialized = "[1,2,3]"; // (1)
    List<Integer> deserialized = new Gson().fromJson(
            serialized,
            new TypeToken<List<Integer>>(){}.getType() // (2)
    );
    Truth.assertThat(deserialized)
            .isEqualTo(
        Lists.newArrayList(1, 2, 3)
    );

}

这里注意一下(2)处的用法就可以,Java里面的泛型,并非所有的地方都会擦除,类定义时还是会保留的(通过类文件的元数据),具体见另一篇笔记和对Guava类库中TypeToken类的分析。

如果使用默认的=new Gson()=,那么List返回的类型就是=ArrayList=:

// 接上一个代码片段:
Truth.assertThat(deserialized.getClass())
    .isAssignableTo(ArrayList.class);

如果希望定制化类型的序列化和反序列化,可以使用=GsonBuilder=中的=registerTypeAdapter=方法,下面是几个例子。

反序列化Json中的数组为Guava中的ImmutableList

这个参考了StackOverflow上的一个解答:Deserializing ImmutableList using Gson, 先定义出一个=gsonBuilder=:

private GsonBuilder gsonBuilder = new GsonBuilder();

然后调用=registerTypeAdapter=方法:

gsonBuilder.registerTypeAdapter(
    List.class, // (1)
    new JsonDeserializer<List<?>>() { // (2)
        @Override
        public List<?> deserialize(JsonElement json,
                                   Type typeOfT,
                                   JsonDeserializationContext context)
                throws JsonParseException {
            List<? super Object> rawList = new ArrayList<>();
            Type elementType = ((ParameterizedType)typeOfT).getActualTypeArguments()[0];
            for (JsonElement jsonElement : json.getAsJsonArray()) {
                rawList.add(context.deserialize(jsonElement, elementType));
            }
            return ImmutableList.copyOf(rawList);
        }
    }
);

这里(1)所传入的类对象类型,对应了泛型中所需要反序列化的类型,如

gsonBuilder.create().fromJson(
    serialized, // serialized data,
    new TypeToken<List<Integer>>(){}.getType() // (3)
);

因为上面的代码片段中(3)的位置用了List,因此会被更前一个代码片段所定义的JsonDeserializer所反序列化。

(2)中是定制的反序列化器,所实现的=deserialize=方法中,传入的参数有:

实现中先把传入的类型获取出来:

Type elementType = ((ParameterizedType)typeOfT).getActualTypeArguments()[0];

然后把json强转为JsonArray(这里没有做校验),使用context反序列化后,add到方法体内部所定义的List内,最后返回一个ImmutableList就可以。

这里捕获了调用时声明类型为=List=的反序列化过程,如果像下面这样使用:

List<Integer> deserialized = gsonBuilder.create().fromJson(
        serialized,
        new TypeToken<ArrayList<Integer>>(){}.getType()
);

那具体的反序列化过程并不会使用定制的反序列化器。这个符合原有的设计,因为指定了类型是=ArrayList=、那么反序列化到=ImmutableList=总是不大合理的。但是指定类型为=ImmutableList=也是无法被捕获:

List<Integer> deserialized = gsonBuilder.create().fromJson(
        serialized,
        new TypeToken<ImmutableList<Integer>>(){}.getType()
);

为了解决这种情况、可以把=ImmutableList.class=的反序列化也定制出来,因为相应的反序列过程和上面一般的=List.class=对应的过程一致,因此可以这样写:

JsonDeserializer<List<?>> jsonDeserializer = new JsonDeserializer<List<?>>() {
    @Override
    public List<?> deserialize(JsonElement json,
                               Type typeOfT,
                               JsonDeserializationContext context)
            throws JsonParseException {
        List<? super Object> rawList = new ArrayList<>();
        Type elementType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
        for (JsonElement jsonElement : json.getAsJsonArray()) {
            rawList.add(context.deserialize(jsonElement, elementType));
        }
        return ImmutableList.copyOf(rawList);
    }
};
gsonBuilder.registerTypeAdapter( List.class, jsonDeserializer );
gsonBuilder.registerTypeAdapter( ImmutableList.class, jsonDeserializer );

甚至可以传入一个可能带有参数信息的类型,如:

gsonBuilder.registerTypeAdapter( 
    new TypeToken<List<Integer>>(){}.getType(), // (1)
    jsonDeserializer );

如(1)处所示,使用List代替了一般的List,这样使得定制的反序列化器只针对List生效:

String serialized = "[1,2,3]";
List<Integer> deserialized = gsonBuilder.create().fromJson(
        serialized,
        new TypeToken<List<String>>(){}.getType()
);
Truth.assertThat(deserialized)
        .isEqualTo(
                Lists.newArrayList("1", "2", "3")
        );
Truth.assertThat(deserialized.getClass()).isAssignableTo(ArrayList.class);

另一点要注意的是,这里反序列化时传入的类型参数和最后所定义的类型参数,编译器是不会校验的:

List<Integer> deserialized = // (1)
    gsonBuilder.create().fromJson(
            serialized,
            new TypeToken<ImmutableList<String>>(){}.getType() // (2)
    );

如上面的代码片段中,(1)处声明的变量类型为=List<Integer>=,但是(2)处传入的类型信息为=ImmutableList<String>=,则编译期不会报错,但是运行期deserialized的对应类型为=ImmutableList<String>=。

同样的技巧,可以把Map反序列化为ImmutableMap

反序列化Json中的对象为ImmutableMap

Gson默认反序列化Map为HashMap:

String deserialized = "{\"HOURS\": 1, \"DAYS\": 24}";
Map<TimeUnit, Integer> o = new Gson().fromJson(deserialized,
        new TypeToken<Map<TimeUnit, Integer>>() {}.getType());
Truth.assertThat(o.get(TimeUnit.HOURS)).isEqualTo(1);
Truth.assertThat(o.get(TimeUnit.DAYS)).isEqualTo(24);
Truth.assertThat(o.getClass()).isAssignableTo(HashMap.class);

同样的,可以定制反序列化器、来反序列化为ImmutableMap,这里忽略除开=JsonDeserializer=外的其他内容:

JsonDeserializer<Map<?, ?>> mapJsonDeserializer = new JsonDeserializer<Map<?, ?>>() {
    @Override
    public Map<?, ?> deserialize(JsonElement json,
                                 Type typeOfT,
                                 JsonDeserializationContext context) throws JsonParseException {

        Type[] types = ((ParameterizedType) typeOfT).getActualTypeArguments(); // (1)
        Type keyType = types[0];
        Type valueType = types[1];

        Map<? super Object, ? super Object> rawMap = new HashMap<>();
        for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
            rawMap.put(
                    context.deserialize(new JsonPrimitive(entry.getKey()), keyType), // (2)
                    context.deserialize(entry.getValue(), valueType) // (3)
            );
        }
        return ImmutableMap.copyOf(rawMap);
    }
};

定制方式和反序列化为ImmutalbeList大同小异,注意一下key的反序列化,即(2)处即可。

定制Object的反序列化结果

直接指定结果为Object时:

String serialized =
        "{\n" +
        "    \"object\": {\n" +
        "        \"int\": 1,\n" +
        "        \"double\": 1.2,\n" +
        "        \"boolean\": true,\n" +
        "        \"array\": [\n" +
        "            1,\n" +
        "            2,\n" +
        "            3.5\n" +
        "        ]\n" +
        "    }\n" +
        "}";
Object deserialized = new Gson().fromJson(serialized, Object.class);

所得到的运行时结果为:

运行时结果
运行时结果

从图中可见:

  1. json object反序列化为=LinkedTreeMap=
  2. json number(json的规范内没有整数和非整数之分)反序列化为=Double=
  3. json array反序列化为ArrayList
  4. json boolean反序列化为boolean

这里定制一个Object的反序列化器,以达到一下目的:

  1. json object反序列化为=ImmutableMap=
  2. json number反序列化为:整数: Integer,非整数:BigDecimal
  3. json array反序列化为=ImmutableList=

其他都和原生的反序列行为一致。

*但尝试registerTypeAdapter时失败*。这一部分的原因是Object的反序列化器不可以自定义,具体可见:

https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/Gson.java#L224

相关的两个issue:

无奈之下只好另外写一个=copyOf=方法:

public Object copyOf(Object object) {
    if(object instanceof LinkedTreeMap) {
        @SuppressWarnings("unchecked")
        LinkedTreeMap<String, Object> linkedTreeMap = (LinkedTreeMap<String, Object>)object;
        Map<String, Object> internalMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : linkedTreeMap.entrySet()) {
            internalMap.put(entry.getKey(), copyOf(entry.getValue()));
        }
        return ImmutableMap.copyOf(internalMap);
    }
    if(object instanceof ArrayList) {
        List<Object> internalList = new ArrayList<>();
        @SuppressWarnings("unchecked")
        ArrayList<Object> arrayList = (ArrayList<Object>) object;
        for (Object o : arrayList) {
            internalList.add(copyOf(o));
        }
        return ImmutableList.copyOf(internalList);
    }
    if(object instanceof String) {
        return object;
    }
    if(object instanceof Boolean) {
        return object;
    }
    if(object instanceof Double) {
        Double object1 = (Double) object;
        if(DoubleMath.isMathematicalInteger(object1)) {
            return object1.longValue();
        } else {
            return BigDecimal.valueOf(object1);
        }
    }
    return null;
}

这样转换一次后、得到的类型为(续上面的例子):

转换后类型
转换后类型

定制序列化器

考虑如下所示的一个表示时间的类:

import java.util.concurrent.TimeUnit;

public class Time {

    public final int value;
    public final TimeUnit unit;

    public Time(int value, TimeUnit unit) {
        this.value = value;
        this.unit = unit;
    }
}

如果用默认的方式来序列化与反序列化:

Time time = new Time(3, TimeUnit.HOURS);
String expected = "{\"value\":3,\"unit\":\"HOURS\"}"; // (1)
String serialized = new Gson().toJson(time);
Truth.assertThat(serialized).isEqualTo(expected);

从(1)处可以看到最后的枚举值序列化为大写字母=HOURS=。

@Test(expected = AssertionError.class)
public void testTimeDeserialized() throws Exception {
    String serialized = "{\"value\":3,\"unit\":\"hours\"}"; // (2)
    Time time = new Gson().fromJson(serialized, Time.class);
    Truth.assertThat(time.unit).isEqualTo(TimeUnit.HOURS);
}

(2)处将=HOURS=改成了小写的=hours=试图反序列化,此时使用默认的反序列化方式,=unit=成员将会是=null=。

定制序列化器和反序列化器使得满足下面的需求:

  1. 序列化时Time类时单位统一转换为小写。
  2. 反序列化时支持不同的字符串都可以反序列化回TimeUnit。如=hour,hours,HOUR,HOURS=等都可以反序列化回=TimeUnit的HOURS=。

定制序列化器可以像下面这样写:

// registering type adapter for TimeUnit does not work. TODO figure out the reason.
gsonBuilder.registerTypeAdapter(Time.class, new JsonSerializer<Time>() {
    @Override
    public JsonElement serialize(Time src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject object = new JsonObject();
        object.addProperty("value", src.value);
        object.addProperty("unit", src.unit.toString().toLowerCase()); // (1)
        return object;
    }
});

注意(1)处的=toLowerCase()=即可。

反序列化器可以这样写:

gsonBuilder.registerTypeAdapter(TimeUnit.class, new JsonDeserializer<TimeUnit>() {

    @Override
    public TimeUnit deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        String asString = json.getAsString().toUpperCase(); // (1)
        switch (asString) {
            case "NANOSECOND":
            case "NANOSECONDS":
                return TimeUnit.NANOSECONDS;
            case "MICROSECOND":
            case "MICROSECONDS":
                return TimeUnit.MICROSECONDS;
            case "MILLISECOND":
            case "MILLISECONDS":
                return TimeUnit.MILLISECONDS;
            case "SECOND":
            case "SECONDS":
                return TimeUnit.SECONDS;
            case "MINUTE":
            case "MINUTES":
                return TimeUnit.MINUTES;
            case "HOUR":
            case "HOURS":
                return TimeUnit.HOURS;
            case "DAY":
            case "DAYS":
                return TimeUnit.DAYS;
            default:
                return null;
        }
    }
});

也很简单,注意(1)处的=toUpperCase()=皆可。

最后的测试代码:


@Test
public void testCustomTimeSerialized() throws Exception {
    Time time = new Time(3, TimeUnit.HOURS);
    String expected = "{\"value\":3,\"unit\":\"hours\"}";
    String serialized = GsonHelper.newGson().toJson(time);
    Truth.assertThat(serialized).isEqualTo(expected);
}

@Test
public void testCustomTimeDeserialized() throws Exception {
    String serialized = "{\"value\":3,\"unit\":\"hours\"}";
    Time time = GsonHelper.newGson().fromJson(serialized, Time.class);
    Truth.assertThat(time.unit).isEqualTo(TimeUnit.HOURS);
}

定义基于基类的反序列化器

这里在另一个小的规则引擎里面有用到,具体可以看看那边的一个反序列化例子。

附录

测试代码:https://gist.github.com/onriv/9818e653f46ff298d7e3933d56d31ea1