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

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

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

简介: 工作中可能会遇到对 Map<String,Object> 进行 JSON 序列化,其中值中包含 Long 类型的数据,反序列化后强转 Long 时报类型转换异常的问题。 本文简单探讨下该问题,并给出解决方案,如果你想直接看建议,直接翻到第三部分即可。
+关注继续查看

一、背景

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

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

二、研究

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

   <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.13.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.8</version>
        </dependency>
   

代码示例

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<String, Object> 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<String, Object> fastMap = JSON.parseObject(jsonStr, new com.alibaba.fastjson.TypeReference<Map<String, Object>>() {
        });
        printMap(fastMap);

        System.out.println("--- gson -----");
        Map<String, Object> gsonMap = new GsonBuilder().create()
                .fromJson(jsonStr, (new TypeReference<Map<String, Object>>(){}).getType() );
        printMap(gsonMap);

        System.out.println("--- jackson -----");
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> jacksonMap = objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {
        });
        printMap(jacksonMap);
    }

    private static void printMap(Map<String, Object> 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<String,Object> 类型,当你只知道这些信息时,你无法得知 aLong 原始类型为 Long 。

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


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

 Map<String, Object> 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<String, Object> dataMap = new HashMap<>(2);
        dataMap.put("aInteger", 1);
        dataMap.put("aFLoat", 0.1F);

运行结果:

{"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

在这里插入图片描述
通过 com.alibaba.fastjson.parser.deserializer.MapDeserializer#parseMap 对 Map 类型进行解析。

在这里插入图片描述
由于 Map<String, Object> 的 valueType 类型为 Object,因此对
aFloat 使用 JavaObjectDeserializer 反序列化器进行解析。
在这里插入图片描述
跟进 lexer.decimalValue 看下:
在这里插入图片描述
最终通过 com.alibaba.fastjson.parser.JSONScanner#decimalValue 将 aFloat 解析为 BigDecimal 类型。

三、如何解决

3.1 慎对 Map<String,Object> 序列化

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

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

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


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

3.2 反序列化自定义类

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

@lombok.Data
public class Data {
    private Float aFloat;
    private Integer aInteger;
}
  Map<String, Object> 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)

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

Json2Pojo IDEA 插件

和一些在线生成工具:

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

在这里插入图片描述
https://www.javainuse.com/pojo

在这里插入图片描述

3.3 其他

可能网上还会有其他解决方案,比如自定义序列化和反序列化器。
我个人不太建议这么做,因为这样不够通用,跨系统使用不太方便。

四、总结

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

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

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

相关文章
java反序列化漏洞入门分析
参考文献: https://nickbloor.co.uk/2017/08/13/attacking-java-deserialization/amp/https://www.
1582 0
【Filter 页面重定向循环】写一个过滤器造成的页面重定向循环的问题
今天做一个过滤器,碰上页面重定向循环的情况: 浏览器的访问路径是:http://192.168.16.104:8080/biologyInfo/login/login/login/login/login/login/login/login/login/login/login/login/login...
926 0
Serializable详解(1):代码验证Java序列化与反序列化
Serializable详解之代码验证Java序列化与反序列化
921 0
transient:将属性脱离序列化 | 带你学《Java语言高级特性》之七十一
transient关键字是类似于static、final等关键字的修饰符,它可以使类中的属性在序列化时跳过该属性,本节将为读者介绍其相关内容与用法。
528 0
匿名内部类方式构建对象导致序列化失败
###问题描述: 以下代码为问题代码: ``` public class ItemDO implements Serializable { private static final long serialVersionUID=-463144769925355007L; ... private Map langAndTitleMap; ...
1694 0
Java反序列化漏洞执行命令回显实现及Exploit下载
原文地址:http://www.freebuf.com/tools/88908.html   本文原创作者:rebeyond 文中提及的部分技术、工具可能带有一定攻击性,仅供安全学习和教学用途,禁止非法使用! 0×00 前言 前段时间java 的反序列化漏洞吵得沸沸扬扬,从刚开始国外某牛的一个可以执行OS命令的payload生成器,到后来的通过URLClassLoader来加载远程类来反弹shell。
1264 0
+关注
明明如月
阿里巴巴 资深Java开发工程师
337
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载