1.问题复现
json long类型反序列化时,出现精度丢失问题,测试代码:
@Test
public void t1() {
String j = "{\"id\":123456789012345678, \"type\":\"auto\"}";
Map m = new Gson().fromJson(j, Map.class);
System.out.println(m);
}
打印:{id=1.2345678901234568E17, type=auto}
2.分析原因
debug可以发现对于Map类型,其Gson对应的adapter为MapTypeAdapterFactory,在读取json值时对应的adapter为ObjectTypeAdapter。
ObjectTypeAdapter解析json值主要在read方法中:
@Override public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
List<Object> list = new ArrayList<Object>();
in.beginArray();
while (in.hasNext()) {
list.add(read(in));
}
in.endArray();
return list;
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<String, Object>();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map;
case STRING:
return in.nextString();
case NUMBER:
return in.nextDouble();
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
throw new IllegalStateException();
}
}
可以看到数字类型NUMBER读取时转换为了double类型。
double类型整数最多16位(十进制),所以超过16就会存在精度丢失。
3.解决方法
a.声明反序列化具体类型。
声明反序列化具体类型,Gson会匹配对应的adapter,Long类型对应的adapter为:
public static final TypeAdapter<Number> LONG = new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
try {
return in.nextLong();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value);
}
};
可以看到读取的值转换为long类型,不存在精度问题。
b.通过自定义TypeAdapter改变其NUMBER类型转换规则,特殊处理Long类型,下面的CustomObjectTypeAdapter将大于Integer.MAX_VALUE都按string类型返回:
public static final class CustomObjectTypeAdapter extends TypeAdapter<Object> {
CustomObjectTypeAdapter() {
}
@Override
public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
List<Object> list = new ArrayList<>();
in.beginArray();
while (in.hasNext()) {
list.add(read(in));
}
in.endArray();
return list;
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<>();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map;
case STRING:
return in.nextString();
case NUMBER:
String s = in.nextString();
double d = Double.parseDouble(s);
if (d > Integer.MAX_VALUE) {
return s;
} else {
int integer = (int) d;
//noinspection RedundantIfStatement
if (integer == d) {
return integer;
}
return d;
}
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
throw new IllegalStateException();
}
}
@Override
public void write(JsonWriter out, Object value) {
}
}
将其添加到Gson 反序列化adapter中:
GsonBuilder builder = new GsonBuilder();
builder.setLongSerializationPolicy(LongSerializationPolicy.STRING);
builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
builder.registerTypeAdapter(Map.class, new CustomObjectTypeAdapter());
GSON = builder.create();
这样map类型反序列化时long类型将返回string类型,eg:
String j = "{\"id\":123456789012345678, \"type\":\"auto\", \"maps\":{123456789012345678: 1234567890123456789}}";
Map m = GSON.fromJson(j, Map.class);
System.out.println(GSON.toJson(m));
输出:{"id":"123456789012345678","type":"auto","maps":{"123456789012345678":"1234567890123456789"}}
对于非map类型,并且没有明确定义的类型(如Object)。反序列化时可以转一遍map再转为目标类型:
if (!(GSON.getAdapter(obj) instanceof CustomObjectTypeAdapter)) {
Map map = GSON.fromJson(json, Map.class);
json = GSON.toJson(map);
}
return GSON.fromJson(json, obj);