JSON 反序列化 Long 变 Integer 或 Double 问题-阿里云开发者社区

开发者社区> 明明如月> 正文

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

简介: 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 其他

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

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


四、总结

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


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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
从Intellij IDEA14 SpringMVC4+Hibernate4问题得到的启发
1、在添加model类hibernate注解的时候,idea一直提示没有配置数据源(其实是假报错,浪费我这么长时间,感觉idea还是和vs有很大的差距)! 2、解决上面的问题,又报错,原来id的注解写成了自动生成(@GeneratedValue(strategy=GenerationType.AUTO)),而id的类型是sting改成int就可以了。
522 0
java反序列化漏洞入门分析
参考文献: https://nickbloor.co.uk/2017/08/13/attacking-java-deserialization/amp/https://www.
1671 0
匿名内部类方式构建对象导致序列化失败
###问题描述: 以下代码为问题代码: ``` public class ItemDO implements Serializable { private static final long serialVersionUID=-463144769925355007L; ... private Map langAndTitleMap; ...
1751 0
解决Form中ExternalInterface的Bug问题
加一个在你嵌套flash的form里的标记后面: //这里是你的flash代码内容。   感谢你的提醒.这也有一个不错的方法http://kb.adobe.com/selfservice/viewContent.do?externalId=kb400730&sliceId=2
613 0
org.json (json序列化工具)
有时候,我们只想利用json串中的部分信息,如何像js一样方便地拿到指定成员的值呢? 可以用json工具。 获得 &lt;dependency&gt; &lt;groupId&gt;org.json&lt;/groupId&gt; &lt;artifactId&gt;json&lt;/artifactId&gt; &lt;version&gt;20141113&lt;/
1028 0
Integer的自动拆装箱的陷阱(整型数-128到127的值比较问题)
public static void main(String[] a) { test1(); } static void test1() { // Integer的自动拆装箱的陷阱(整型数-128到127的值比较问题) out.
922 0
+关注
明明如月
阿里巴巴 资深Java开发工程师
343
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载