JSON 反序列化 Long 变 Integer 或 Double 问题

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JSON 反序列化 Long 变 Integer 或 Double 问题

一、背景

工作中可能会遇到对 Map 进行 JSON 序列化,其中值中包含 Long 类型的数据,反序列化后强转 Long 时报类型转换异常的问题。


本文简单探讨下该问题,并给出解决方案,如果你想直接看建议,直接翻到第三部分即可。


二、研究

本文主要以 jackson、 gson、fastjson 三个库为例,版本分别如下:


          com.fasterxml.jackson.core

          jackson-core

          2.13.0      

     

          com.fasterxml.jackson.core

          jackson-databind

          2.13.0                

          com.alibaba

          fastjson

          1.2.78                

          com.google.code.gson

          gson

          2.8.8

代码示例

package json;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.GsonBuilder;
import java.util.HashMap;
import java.util.Map;
public class ObjectDemo {
    public static void main(String[] args) throws JsonProcessingException {
        Map dataMap = new HashMap<>(2);
        dataMap.put("aInteger", 1);
        dataMap.put("aLong", 2L);
        String jsonStr = JSON.toJSONString(dataMap);
        System.out.println(jsonStr);
        // fastjson
        System.out.println("--- fastjson -----");
        Map fastMap = JSON.parseObject(jsonStr, new com.alibaba.fastjson.TypeReference>() {
        });
        printMap(fastMap);
        System.out.println("--- gson -----");
        Map gsonMap = new GsonBuilder().create()
                .fromJson(jsonStr, (new TypeReference>(){}).getType() );
        printMap(gsonMap);
        System.out.println("--- jackson -----");
        ObjectMapper objectMapper = new ObjectMapper();
        Map jacksonMap = objectMapper.readValue(jsonStr, new TypeReference>() {
        });
        printMap(jacksonMap);
    }
    private static void printMap(Map map) {
        map.forEach((key, value) -> {
            System.out.println("key:" + key + ",value=" + value + ",valueClass=" + value.getClass());
        });
    }
}
运行结果:{"aInteger":1,"aLong":2}
--- fastjson -----
key:aLong,value=2,valueClass=class java.lang.Integer
key:aInteger,value=1,valueClass=class java.lang.Integer
--- gson -----
key:aInteger,value=1.0,valueClass=class java.lang.Double
key:aLong,value=2.0,valueClass=class java.lang.Double
--- jackson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aLong,value=2,valueClass=class java.lang.Integer



aLong 虽然原始类型为 Long 但是 fastjson 和 jackson 中被反序列化为 Integer 类型,gson 中被映射为 Double 类型。


我们观察序列化后的 json 字符串:

{"aInteger":1,"aLong":2}

会发现其实 JSON 中并没有包含类型信息,而反序列化的类型为 Map.class 或者 Map 类型,当你只知道这些信息时,你无法得知 aLong 原始类型为 Long 。


因此不同的JSON 序列化工具给出了自己的默认处理行为。


当我们把 aLong 的值调整到 超过 (Integer.MAX_VALUE,Long.MAX_VALUE] 的范围之间时,fastjson 和 jackson 可以解析为 Long 类型。


Map dataMap = new HashMap<>(2);

       dataMap.put("aInteger", 1);

       dataMap.put("aLong", Long.MAX_VALUE);


输出的结果:


{"aInteger":1,"aLong":9223372036854775807}
--- fastjson -----
key:aLong,value=9223372036854775807,valueClass=class java.lang.Long
key:aInteger,value=1,valueClass=class java.lang.Integer
--- gson -----
key:aInteger,value=1.0,valueClass=class java.lang.Double
key:aLong,value=9.223372036854776E18,valueClass=class java.lang.Double
--- jackson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aLong,value=9223372036854775807,valueClass=class java.lang.Long


我们大致了解到, fastjson 和 jackson 默认情况下整数类型优先选取 Integer ,超过 Integer 范围再选择 Long ,以此类推。


而当我们放入 Float 类型时,结果又有差异:


  Map dataMap = new HashMap<>(2);

       dataMap.put("aInteger", 1);

       dataMap.put("aFLoat", 0.1F);

1

2

3

运行结果:

{"aInteger":1,"aFLoat":0.1}
--- fastjson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aFLoat,value=0.1,valueClass=class java.math.BigDecimal
--- gson -----
key:aInteger,value=1.0,valueClass=class java.lang.Double
key:aFLoat,value=0.1,valueClass=class java.lang.Double
--- jackson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aFLoat,value=0.1,valueClass=class java.lang.Double

fastjson 中 Float 被解析为 BigDecimal, gson 和 jackson 中被解析为 Double 类型。


具体底层如何处理,大家可以对每个框架的反序列方法单步跟进去即可得到答案。


这里以 fastjson 为例,简单调试下:

fastjson 底通过 com.alibaba.fastjson.parser.ParserConfig#getDeserializer 方法获取当前类型的反序列化器为 MapDeserializer


执行其反序列化方法:

com.alibaba.fastjson.parser.deserializer.MapDeserializer#deserialze

————————————————

版权声明:本文为CSDN博主「明明如月学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/w605283073/article/details/120797043

5555555.png


通过 com.alibaba.fastjson.parser.deserializer.MapDeserializer#parseMap 对 Map 类型进行解析。

22222.png

由于 Map的 valueType 类型为 Object,因此对

aFloat 使用 JavaObjectDeserializer 反序列化器进行解析。

44.png

三、如何解决

3.0 将类型写入 JSON 字符串中

如果我们能将原始类型写入到 JSON 字符串中,那么反序列化时自然就可以复原原始的类型。

在 fastjson 中可以使用 SerializerFeature.WriteClassName
package json;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.HashMap;
import java.util.Map;
public class JsonDemo {
    public static void main(String[] args) {
        Map dataMap = new HashMap<>(2);
        dataMap.put("aInteger", 1);
        dataMap.put("aLong", 2L);
        dataMap.put("aFloat", 3F);
        String jsonStr = JSON.toJSONString(dataMap, SerializerFeature.WriteClassName);
        System.out.println(jsonStr);
        // fastjson
        System.out.println("--- fastjson -----");
        Map fastMap = JSON.parseObject(jsonStr, new com.alibaba.fastjson.TypeReference>() {
        });
        printMap(fastMap);
    }
    private static void printMap(Map map) {
        map.forEach((key, value) -> {
            System.out.println("key:" + key + ",value=" + value + ",valueClass=" + value.getClass());
        });
    }
}


打印的结果


{"@type":"java.util.HashMap","aFloat":3.0F,"aInteger":1,"aLong":2L}

--- fastjson -----

key:aLong,value=2,valueClass=class java.lang.Long

key:aFloat,value=3.0,valueClass=class java.lang.Float

key:aInteger,value=1,valueClass=class java.lang.Integer


虽然,这种方法可以解决问题,但是这也通常要求序列化和反序列化使用同一个 JSON 工具。


比如上面的 {"@type":"java.util.HashMap","aFloat":3.0F,"aInteger":1,"aLong":2L} 直接使用 jackson 进行反序列化会报错:


System.out.println("--- jackson -----");

       ObjectMapper objectMapper = new ObjectMapper();

       Map jacksonMap = objectMapper.readValue(jsonStr, new TypeReference>() {

       });

       printMap(jacksonMap);

报错内容:

--- jackson -----
Exception in thread "main" com.fasterxml.jackson.core.JsonParseException: Unexpected character ('F' (code 70)): was expecting comma to separate Object entries
 at [Source: (String)"{"@type":"java.util.HashMap","aFloat":3.0F,"aInteger":1,"aLong":2L}"; line: 1, column: 43]
  at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391)
  at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735)
  at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:659)


3.2 提供 POJO 类,慎对 Map 序列化

强烈建议不要怕麻烦,直接定义 POJO 类。

不仅不受 JSON 框架的约束,而且对方解析时也非常明确,不容易出错。


如工作中在发送MQ 消息时很多人图方便,不想定义POJO 对象,因为这样通常需要打包比较麻烦,就将要传输给其他系统的数据定义为 Map 类型,下游再根据 key 去解析,这是一个非常不好的习惯。


很容易造成上下游类型不一致,造成更换 JSON 反序列化工具时出现故障。


因此发送 MQ 消息时,最好给出相应的 POJO 类。


实际工作中,还遇到有同学将 Map 使用 JSON 序列化的方式存储到 Redis 中,然后反序列化后,将原本 Long 类型的值,强转为 Long 导致线上出现BUG(前面讲到,这种情况下使用 fastjson 时,如果值小于整数最大值,反序列化为 Integer 类型,强转必然会报错)。


3.2 反序列化自定义类

如果上游序列化是 Map, 如果类型核实清楚,我们依然可以自定义 POJO 类来反序列化。

@lombok.Data
public class Data {
    private Float aFloat;
    private Integer aInteger;
}
  Map dataMap = new HashMap<>(2);
        dataMap.put("aInteger", 1);
        dataMap.put("aFLoat", 0.1F);
        String jsonStr = JSON.toJSONString(dataMap);
        Data data = JSON.parseObject(jsonStr, Data.class);
        System.out.println(data);


输出结果:

Data(aFloat=0.1, aInteger=1)

2.png

可能有些同学会觉得定义 POJO 类很麻烦,其实我们可以使用 IDEA 插件或者在线工具实现 JSON 字符串生成 POJO 类。


如 Json2Pojo IDEA 插件


和一些在线生成工具:


https://json2csharp.com/json-to-pojo


https://www.javainuse.com/pojo

1.png


3.3 其他

可能网上还会有其他解决方案,比如自定义序列化和反序列化器。

我个人不太建议这么做,因为这样不够通用,跨系统使用不太方便。


四、总结

希望大家能够【坚持做正确的事情】,而不是以是否【麻烦】作为是否采用某种策略的标准。


我们不仅要实现功能,还要充分考虑设计的可拓展性、可维护性等。

相关文章
|
5月前
|
XML 存储 JSON
Twaver-HTML5基础学习(19)数据容器(2)_数据序列化_XML、Json
本文介绍了Twaver HTML5中的数据序列化,包括XML和JSON格式的序列化与反序列化方法。文章通过示例代码展示了如何将DataBox中的数据序列化为XML和JSON字符串,以及如何从这些字符串中反序列化数据,重建DataBox中的对象。此外,还提到了用户自定义属性的序列化注册方法。
60 1
|
3月前
|
JSON JavaScript 前端开发
Go语言中json序列化的一个小坑,建议多留意一下
在Go语言开发中,JSON因其简洁和广泛的兼容性而常用于数据交换,但其在处理数字类型时存在精度问题。本文探讨了JSON序列化的一些局限性,并介绍了两种替代方案:Go特有的gob二进制协议,以及msgpack,两者都能有效解决类型保持和性能优化的问题。
77 7
|
3月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
69 4
|
3月前
|
JSON JavaScript Java
对比JSON和Hessian2的序列化格式
通过以上对比分析,希望能够帮助开发者在不同场景下选择最适合的序列化格式,提高系统的整体性能和可维护性。
105 3
|
3月前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
3月前
|
JSON JavaScript 前端开发
|
6月前
|
Java
【Java基础面试十】、何对Integer和Double类型判断相等?
这篇文章讨论了如何在Java中正确比较`Integer`和`Double`类型的值,指出不能直接使用`==`操作符比较不同类型,而应该将它们转换为相同的基本数据类型(如`double`)后进行比较。
【Java基础面试十】、何对Integer和Double类型判断相等?
|
5月前
|
JSON Go 数据格式
Golang语言结构体链式编程与JSON序列化
这篇文章是关于Go语言中结构体链式编程与JSON序列化的教程,详细介绍了JSON格式的基本概念、结构体的序列化与反序列化、结构体标签的使用以及如何实现链式编程。
61 4
|
6月前
|
存储 JSON JavaScript
python序列化: json & pickle & shelve 模块
python序列化: json & pickle & shelve 模块
|
5月前
|
JSON 数据格式
序列化 json和pickle
序列化 json和pickle

热门文章

最新文章