简体   繁体   English

为什么 Gson 将 Integer 解析为 Double?

[英]Why does Gson parse an Integer as a Double?

A complex json string and I want to convert it to map, I have a problem.一个复杂的 json 字符串,我想将它转换为 map,我遇到了问题。

Please look at this simple test:请看这个简单的测试:

public class Test {

    @SuppressWarnings("serial")
    public static void main(String[] args) {
        Map<String, Object> hashMap = new HashMap<String, Object>();
        hashMap.put("data", "{\"rowNum\":0,\"colNum\":2,\"text\":\"math\"}");

        Map<String,Object> dataMap = JsonUtil.getGson().fromJson(
                hashMap.get("data").toString(),new TypeToken<Map<String,Object>>() {}.getType());

        System.out.println(dataMap.toString());

    }
}

result:结果:
console print: {rowNum=0.0, colNum=2.0, text=math}控制台打印: {rowNum=0.0, colNum=2.0, text=math}
Int is converted to Double; Int转Double;
Why does gson change the type and how can I fix it?为什么 gson 会更改类型,我该如何解决?

Gson is a simple parser. Gson是一个简单的解析器。 It uses always Double as a default number type if you are parsing data to Object . 如果要将数据解析为Object它将始终使用Double作为默认数字类型。

Check this question for more information: How to prevent Gson from expressing integers as floats 检查此问题以获取更多信息: 如何防止Gson将整数表示为浮点数

I suggest you to use Jackson Mapper . 我建议您使用Jackson Mapper Jackson distinguish between type even if you are parsing to an Object: 即使您解析到对象,Jackson也可以区分类型:

  • "2" as Integer "2"Integer
  • "2.0" as Double "2.0"Double

Here is an example: 这是一个例子:

Map<String, Object> hashMap = new HashMap<String, Object>();
hashMap.put("data", "{\"rowNum\":0,\"colNum\":2,\"text\":\"math\"}");
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};

HashMap<String, Object> o = mapper.readValue(hashMap.get("data").toString(), typeRef);

maven: 专家

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>

JSON makes no distinction between the different type of numbers the way Java does. JSON不会像Java一样区分不同类型的数字。 It sees all kind of numbers as a single type. 它会将所有数字视为一种类型。

That the numbers are parsed as a Double is an implementation detail of the Gson library. 将数字解析为Double是Gson库的实现细节。 When it encounters a JSON number, it defaults to parsing it as a Double . 遇到JSON数字时,默认将其解析为Double

Instead of using a Map , it would be better to define a POJO that encapsulates all fields of the JSON structure. 与其使用Map ,不如定义一个封装了JSON结构所有字段的POJO更好。 This makes it much easier to access the data afterwards and the numbers are automatically parsed as an Integer . 这使得以后访问数据变得更加容易,并且数字将自动解析为Integer

class Cell {
    private Integer rowNum;
    private Integer colNum;
    private String text;
}

public static void main(String[] args) throws Exception {
    Map<String, Object> hashMap = new HashMap<String, Object>();
    hashMap.put("data", "{\"rowNum\":0,\"colNum\":2,\"text\":\"math\"}");

    Cell cell = new Gson().fromJson(hashMap.get("data").toString(), Cell.class);
    System.out.println(cell);
}

I needed to get this working as well.我也需要让它工作。 It makes sense why GSON does it this way when de-serializing from a JSON string to a Map, since a Double is the largest container for a number.当从 JSON 字符串反序列化为 Map 时,GSON 这样做是有道理的,因为 Double 是数字的最大容器。

In my case it is very inconvenient if the types change so I implemented what could be considered a hack, but it works so here it is.在我的例子中,如果类型发生变化,那将是非常不方便的,所以我实现了可以被认为是 hack 的东西,但它确实有效,所以它就是这样。

The starting point is a function to take the JSON string and guess if it is a List, Map or a plain String起点是 function 以获取 JSON 字符串并猜测它是列表、Map 还是普通字符串

public static Object parseJson(String json) {
    if (json == null || json.isBlank()) {
        return null;
    }

    var input = json.trim();
    if (input.startsWith("[")) {
        var list = GSON.fromJson(input, List.class);
        for (Object o : list) {
            if (o instanceof Map) {
                visit((Map)o);
            }
        }
        return list;
    } else if (input.startsWith("{")) {
        var map = GSON.fromJson(input, Map.class);
        visit(map);
        return map;
    } else {
        return GSON.fromJson(input, String.class);
    }
}

I then implemented a visitor pattern (I used Java 11 in this case).然后我实现了一个访客模式(在这种情况下我使用了 Java 11)。 It is a recursive function that visits every property in the map and if the property is Iterable or another Map it will visit those properties as well.它是一个递归的 function,它访问 map 中的每个属性,如果该属性是 Iterable 或另一个 Map,它也将访问这些属性。

public static void visit(Map<String, Object> map) {
    for (var entry : map.entrySet()) {
        var value = entry.getValue();
        if (value == null) {
            continue;
        }
        if (value instanceof Map) {
            visit((Map<String, Object>)value);
        }
        if (value instanceof Iterable) {
            var it = (Iterable)value;
            visit(it, o -> {
                if (o instanceof Map) {
                    visit((Map<String, Object>)o);
                }
            });
        }
        if (value instanceof Double) {
            var d = (Double) value;
            var bigDecimal = new BigDecimal(d);
            long longValue = bigDecimal.longValue();
            var integerPart = String.valueOf(longValue);
            var decimalPart = String.valueOf(bigDecimal.subtract(
                    new BigDecimal(longValue)));
            if (decimalPart.matches("^0+$")) {
                if (longValue > Integer.MAX_VALUE || longValue < Integer.MIN_VALUE) {
                    entry.setValue(d.longValue());
                } else {
                    entry.setValue(d.intValue());
                }
            }
        }
    }
}

public static void visit(Iterable it, Consumer consumer) {
    it.forEach(o -> {
        if (o instanceof Iterable) {
            visit((Iterable) o, consumer);
        } else {
            consumer.accept(o);
        }
    });
}

Each time a Double type is encountered, it tries to determine if it has a decimal point.每次遇到 Double 类型时,它都会尝试确定它是否有小数点。 If it has one it remains a double.如果它有一个,它仍然是一个双。 Otherwise it checks if the integer part should be a Long or Integer type by comparing Integer.MAX_VALUE and Integer.MIN_VALUE respectively.否则,它通过分别比较 Integer.MAX_VALUE 和 Integer.MIN_VALUE 来检查 integer 部分应该是 Long 还是 Integer 类型。

If a type change was detected, then the map entry value is replaced with the new primitive type.如果检测到类型更改,则 map 条目值将替换为新的原始类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM