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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36508323/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 01:32:43  来源:igfitidea点击:

How can I prevent gson from converting integers to doubles

javajsongson

提问by AlexC

I've got integers in my json, and I do not want gson to convert them to doubles. The following does not work:

我的 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);
    }
}

The output is not what I want:

输出不是我想要的:

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

Is there a way to have Integers instead of Doubles in my Map?

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

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

Edit:not all my fields are integer. I've modified my example accordingly. I've read quite a few examples online, including some answers on this site, but it does not work in this particular case.

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

回答by varren

1) You have to create custom JsonDeserializerand not JsonSerializerlike in your question.

1)您必须创建自定义JsonDeserializer而不是JsonSerializer您的问题。

2) I don't think this behavior comes from Doubledeserializer. it is more like json object/map problem

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

Here is from source code:

这是来自源代码

case NUMBER:
      return in.nextDouble();

So you can try approach with custom deserializer for Map<String, Object>(or some more generic map if you want) :

因此,您可以尝试使用自定义反序列化器的方法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;
    }
}

To use it you will have to give proper TypeTokento registerTypeAdapterand gson.fromJsonfunction:

要使用它,您必须提供适当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);

Result:

结果:

{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: It is just one more option you can try. Personally i feel like creating custom object for your json instead of List<Map<String, Integer>>is much cooler and easier to read way

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

回答by cybersoft

Streaming version of @varren's answer:

@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();
        }
    }
}

It is modified version of ObjectTypeAdapter.java. These original lines:

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

case NUMBER:
    return in.nextDouble();

are replaced by this:

被替换为:

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

In this code, number is read as string and number's type is selected based on existence of dot: number is double only if it has a dot in its string representation and it is long otherwise. Such solution preserves original values of source JSON.

在这段代码中,数字被读取为字符串,数字的类型是根据点的存在来选择的:数字只有当它的字符串表示中有一个点时才是双精度的,否则它是长的。这种解决方案保留了源 JSON 的原始值。

This modified adapter could be used as universal if you could register it for Object type but Gson prevents it:

如果您可以将这个修改过的适配器注册为 Object 类型,但 Gson 阻止它,则可以将其用作通用适配器:

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

You have to register this type adapter to those types that you need, e.g. Mapand List:

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

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

Now Gson can deserialize numbers as is.

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

回答by Sanj

You have to use public T fromJson(JsonElement json, Type typeOfT)

你必须使用public T fromJson(JsonElement json, Type typeOfT)

public void keepsIntsAsIs(){
        String json="[{\"id\":1,\"quantity\":2},{\"id\":3,\"quantity\":4}]";
        GsonBuilder gsonBuilder = new GsonBuilder();
        Gson gson = gsonBuilder.create();
        Type objectListType = new TypeToken<List<Map<String, Integer>>>(){}.getType();
        List<Map<String, Integer>> l = gson.fromJson(json, objectListType);
        for(Map<String, Integer> item : l){
            System.out.println(item);
        }
    }

Output:

输出:

{id=1, quantity=2}
{id=3, quantity=4}

[EDIT]

[编辑]

If not all fields are integers then one way to resolve this is to map the json to an object and define a deserializer for that object.

如果并非所有字段都是整数,那么解决此问题的一种方法是将 json 映射到一个对象并为该对象定义一个反序列化器。

Below is the example.

下面是示例。

I am mapping json to IdQuantityNameand IdQuantityDeserializeris the json deserializer.

我将 json 映射到IdQuantityName并且IdQuantityDeserializer是 json 反序列化器。

package com.foo;



import java.lang.reflect.Type;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;

public class TestGSON {

public void keepsIntsAsIs(){
    String json="[{\"id\":1,\"quantity\":2,\"name\":\"apple\"},{\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeHierarchyAdapter(IdQuantityName.class, new IdQuantityDeserializer());
    gsonBuilder.registerTypeAdapter(IdQuantityName.class, new IdQuantityDeserializer());

    Gson gson = gsonBuilder.create();
    Type objectListType = new TypeToken<List<IdQuantityName>>(){}.getType();
    List<IdQuantityName> l = gson.fromJson(json,objectListType);
    for (IdQuantityName idQuantityName : l) {
        System.out.println(idQuantityName);
    }
}



class IdQuantityName{
    private int id;
    private Object quantity;
    private String name;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public Object getQuantity() {
        return quantity;
    }
    public void setQuantity(Object quantity) {
        this.quantity = quantity;
    }
    public Object getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "IdQuantityName [id=" + id + ", quantity=" + quantity
                + ", name=" + name + "]";
    }



}
private  class IdQuantityDeserializer implements JsonDeserializer<IdQuantityName>{

    @Override
    public IdQuantityName deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException {

        JsonObject jo = json.getAsJsonObject();

        IdQuantityName idq = new IdQuantityName();
        idq.setId(jo.get("id").getAsInt());
        idq.setName(jo.get("name").getAsString());

        JsonElement jsonElement = jo.get("quantity");
        if(jsonElement instanceof JsonPrimitive){
            if(((JsonPrimitive) jsonElement).isNumber()){
                idq.setQuantity(jsonElement.getAsInt());
            };
        }
        return idq;

    }
}
public static void main(String[] args) {
    new TestGSON().keepsIntsAsIs();
}
}

回答by Leon

This works fine for me:

这对我来说很好用:

private static class DoubleSerializer implements JsonSerializer<Double> {
    @Override
    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
        return src == src.longValue() ? new JsonPrimitive(src.longValue()) : new JsonPrimitive(src);
    }
}

Gson gson = new GsonBuilder().registerTypeAdapter(Double.class, new DoubleSerializer()).setPrettyPrinting().create();

回答by nitinsridar

Use Hymanson instead of Gson, It solves your problem:

使用 Hymanson 而不是 Gson,它解决了您的问题:

import com.fasterxml.Hymanson.databind.ObjectMapper;

import java.io.IOException; import java.util.Map;

导入 java.io.IOException; 导入 java.util.Map;

public class HymansonMapExample1 {

公共类 HymansonMapExample1 {

public static void main(String[] args) {

    ObjectMapper mapper = new ObjectMapper();
    String json = "{\"name\":\"mkyong\", \"age\":\"37\"}";

    try {

        // convert JSON string to Map
        Map<String, String> map = mapper.readValue(json, Map.class);

        // it works
        //Map<String, String> map = mapper.readValue(json, new TypeReference<Map<String, String>>() {});

        System.out.println(map);

    } catch (IOException e) {
        e.printStackTrace();
    }

}

}

}

回答by anthofo

This worked for me, I have a "specs" field which is a Map<String, Object>:

这对我有用,我有一个“规格”字段,它是Map<String, Object>

public class MyClass {

  public Map<String, Object> specs;

}

Before the fix I was getting this output for a list of these objects:

在修复之前,我获得了这些对象列表的输出:

{  
   "hits":{  
      "content":[  
         {  
            "specs":{  
               "fiscalHorsePower":4.0,
               "nbOfDoors":5.0,
               "consumption":4.3
            }
         }
      ]
   }
}

fiscalHorsePowerand nbOfDoorsare integer.

fiscalHorsePower并且nbOfDoors是整数。

Here is the fix I used, first create a new Adapterand a Factory:

这是我使用的修复程序,首先创建一个新的Adapter和一个Factory

public class CustomizedObjectTypeAdapter extends TypeAdapter<Object> {

public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @SuppressWarnings("unchecked")
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (Map.class.isAssignableFrom(type.getRawType())) {
            return (TypeAdapter<T>) new CustomizedObjectTypeAdapter();
        }
        return null;
    }
};

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();
    }
}
}

And then register the factory:

然后注册工厂:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(CustomizedObjectTypeAdapter.FACTORY);

and here is the result with the fix:

这是修复的结果:

{  
   "hits":{  
      "content":[  
         {  
            "specs":{  
               "fiscalHorsePower":4,
               "nbOfDoors":5,
               "consumption":4.3
            }
         }
      ]
   }
}