gson一直是我用着非常得心应手的json处理工具。但是最近遇到了一个坑,就是在处理java.util.Map型json字符串的时候会把整型转为浮点型。
示例程序如下:
1 2 3 4 5 6 7 8 9 10 |
public static void main(String[] args) { Gson gson = new Gson(); String json = "{\"key1\":1,\"key2\":2.0,\"key3\":\"3\"}"; Map m = gson.fromJson(json, Map.class); System.out.println(m.get("key1")); System.out.println(m.get("key2")); System.out.println(m.get("key3")); } |
执行结果如下:
1 2 3 4 5 |
1.0 2.0 3 Process finished with exit code 0 |
原因在于json的语法中关于数值只有一个number类型,而不会去判断这个number具体是整型是浮点型还是长整型。而Gson处理的时候也确实是偷懒了,统一将之视为浮点型。可以在Gson的ObjectTypeAdapter类中看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
@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(); } } |
在case NUMBER这一行可以看到,数值型的数据都被处理为Double型的值。此外因为这里使用的Map的value类型是Object,在示例代码中就涉及到了Integer、Double和String。所以也不能使用TypeToken这样的解决方案。使用TypeToken的方式如下,适用于有明确的泛型说明的情况:
1 |
Map m = gson.fromJson(json, new TypeToken<Map<String, Integer>>(){}.getType()); |
其它的解决方案也简单:1. 使用其他的json库,比如jackson和fastjson,亲测过,都没有这种问题;2. 添加自定义模块,修改这个问题。
详细说明下方案2。Gson支持添加自定义解析方案,可以使用GsonBuilder的registerTypeAdapter和registerTypeHierarchyAdapter。前者只针对设置的类进行序列化及反序列化,后者可以对设置的类及其子类进行序列化。可以添加的解析类的类型包括JsonSerializer、JsonDeserializer和TypeAdapter这三个接口的实现类。下面是一个使用自定义的JsonDeserializer方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import com.google.gson.*; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) { Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new JsonDeserializer<Map>() { @Override public Map deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); Map p = new HashMap(); for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) { if (e.getValue().isJsonPrimitive()) { p.put(e.getKey(), e.getValue()); } p.put(e.getKey(), e.getValue().getAsString()); } return p; } }).create(); String json = "{\"key1\":1,\"key2\":2.0,\"key3\":\"zhongwen\"}"; Map m = gson.fromJson(json, new TypeToken<Map<String, Object>>() { }.getType()); System.out.println(m.get("key1")); System.out.println(m.get("key2")); System.out.println(m.get("key3")); } } |
这个方案也是有局限性的,只适用于Map的key和value都是数值型和字符串的情况(也可以添加对boolean的支持)。对于复杂的Map结构就有些无力了。
此外另一个方案是根据ObjectTypeAdapter自定义TypeAdapter。这个我也试过,功能支持还算可以,它解析json的方式是分层实现的。不过如果目标是具体类还可以,对于抽象类或接口的适用性就差一些。因为关键是json最外层的解析,如果是json对应的是对象的话,也就只能封装成一个目标类型对象或其超类对象。
先就这样。
#############
发表评论