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=方法中,传入的参数有:
- JsonElement json:json元素,对应了Json所定义的类型,如JsonArray,JsonObject,JsonPrimitive
- Type typeOfT:传入的指定类型,如上面的代码片段中,这个类型就是=List<Integer>=(更多关于Type、ParameterizedType、Class等类的内容见另一个笔记)。
- JsonDeserializationContext context:*这个还不大清楚,待整理添加*。
实现中先把传入的类型获取出来:
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);
所得到的运行时结果为:
从图中可见:
- json object反序列化为=LinkedTreeMap=
- json number(json的规范内没有整数和非整数之分)反序列化为=Double=
- json array反序列化为ArrayList
- json boolean反序列化为boolean
这里定制一个Object的反序列化器,以达到一下目的:
- json object反序列化为=ImmutableMap=
- json number反序列化为:整数: Integer,非整数:BigDecimal
- 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=。
定制序列化器和反序列化器使得满足下面的需求:
- 序列化时Time类时单位统一转换为小写。
- 反序列化时支持不同的字符串都可以反序列化回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