如何防止gson将整数转换为双精度值

2022-09-01 15:14:18

我的json中有整数,我不希望gson将它们转换为双精度。以下情况不起作用:

@Test
public void keepsIntsAsIs(){
    String json="[{\"id\":1,\"quantity\":2,\"name\":\"apple\"},{\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Double.class,  new DoubleSerializerAsInt());
    Gson gson = gsonBuilder.create();
    List<Map<String, Object>> l = gson.fromJson(json, List.class);
    for(Map<String, Object> item : l){
        System.out.println(item);
    }
}

private static class DoubleSerializerAsInt implements JsonSerializer<Double>{

    @Override
    public JsonElement serialize(Double aDouble, Type type, JsonSerializationContext jsonSerializationContext) {
        int value = (int)Math.round(aDouble);
        return new JsonPrimitive(value);
    }
}

输出不是我想要的:

{id=1.0, quantity=2.0, name=apple}
{id=3.0, quantity=4.0, name=orange}

有没有办法在我的地图中使用整数而不是双精度值?

{id=1, quantity=2, name=apple}
{id=3, quantity=4, name=orange}

编辑:并非所有我的字段都是整数。我相应地修改了我的示例。我在网上阅读了不少例子,包括这个网站上的一些答案,但它在这种特殊情况下不起作用。


答案 1

1)您必须创建自定义,而不是像您的问题一样。JsonDeserializerJsonSerializer

2)我不认为这种行为来自反序列化器。它更像是json对象/映射问题Double

这是来自源代码

case NUMBER:
      return in.nextDouble();

因此,您可以尝试使用自定义反序列化程序(或者如果需要,可以尝试使用更通用的映射)的方法:Map<String, Object>

public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer<Map<String, Object>>{

    @Override  @SuppressWarnings("unchecked")
    public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return (Map<String, Object>) read(json);
    }

    public Object read(JsonElement in) {

        if(in.isJsonArray()){
            List<Object> list = new ArrayList<Object>();
            JsonArray arr = in.getAsJsonArray();
            for (JsonElement anArr : arr) {
                list.add(read(anArr));
            }
            return list;
        }else if(in.isJsonObject()){
            Map<String, Object> map = new LinkedTreeMap<String, Object>();
            JsonObject obj = in.getAsJsonObject();
            Set<Map.Entry<String, JsonElement>> entitySet = obj.entrySet();
            for(Map.Entry<String, JsonElement> entry: entitySet){
                map.put(entry.getKey(), read(entry.getValue()));
            }
            return map;
        }else if( in.isJsonPrimitive()){
            JsonPrimitive prim = in.getAsJsonPrimitive();
            if(prim.isBoolean()){
                return prim.getAsBoolean();
            }else if(prim.isString()){
                return prim.getAsString();
            }else if(prim.isNumber()){

                Number num = prim.getAsNumber();
                // here you can handle double int/long values
                // and return any type you want
                // this solution will transform 3.0 float to long values
                if(Math.ceil(num.doubleValue())  == num.longValue())
                   return num.longValue();
                else{
                    return num.doubleValue();
                }
           }
        }
        return null;
    }
}

要使用它,您必须正确使用并发挥作用:TypeTokenregisterTypeAdaptergson.fromJson

String json="[{\"id\":1,\"quantity\":2,\"name\":\"apple\"}, {\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";

GsonBuilder gsonBuilder = new GsonBuilder();

gsonBuilder.registerTypeAdapter(new TypeToken<Map <String, Object>>(){}.getType(),  new MapDeserializerDoubleAsIntFix());

Gson gson = gsonBuilder.create();
List<Map<String, Object>> l = gson.fromJson(json, new TypeToken<List<Map<String, Object>>>(){}.getType() );

for(Map<String, Object> item : l)
    System.out.println(item);

String serialized = gson.toJson(l);
System.out.println(serialized);

结果:

{id=1, quantity=2, name=apple}
{id=3, quantity=4, name=orange}
Serialized back to: [{"id":1,"quantity":2,"name":"apple"},{"id":3,"quantity":4,"name":"orange"}]

PS:这只是您可以尝试的另一种选择。就个人而言,我觉得为您的json创建自定义对象,而不是更酷,更易于阅读的方式List<Map<String, Integer>>


答案 2

@varren的答案的流媒体版本:

class CustomizedObjectTypeAdapter extends TypeAdapter<Object> {

    private final TypeAdapter<Object> delegate = new Gson().getAdapter(Object.class);

    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        delegate.write(out, value);
    }

    @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();
                String n = in.nextString();
                if (n.indexOf('.') != -1) {
                    return Double.parseDouble(n);
                }
                return Long.parseLong(n);

            case BOOLEAN:
                return in.nextBoolean();

            case NULL:
                in.nextNull();
                return null;

            default:
                throw new IllegalStateException();
        }
    }
}

它是ObjectTypeAdapter的修改版本.java。这些原始行:

case NUMBER:
    return in.nextDouble();

替换为:

case NUMBER:
    String n = in.nextString();
    if (n.indexOf('.') != -1) {
        return Double.parseDouble(n);
    }
    return Long.parseLong(n);

在此代码中,数字被读作字符串,数字的类型是根据点的存在来选择的:只有当数字的字符串表示形式中有一个点并且它很长时,它才是双精度的。此类解决方案保留了源 JSON 的原始值。

如果您可以将此修改后的适配器注册为对象类型,则可以将其用作通用适配器,但 Gson 会阻止它:

// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);

您必须将此类型适配器注册到您需要的那些类型,例如 和:MapList

CustomizedObjectTypeAdapter adapter = new CustomizedObjectTypeAdapter();
Gson gson = new GsonBuilder()
        .registerTypeAdapter(Map.class, adapter)
        .registerTypeAdapter(List.class, adapter)
        .create();

现在,Gson 可以按原样反序列化数字。