千家信息网

FastJson long溢出问题怎么解决

发表于:2025-01-17 作者:千家信息网编辑
千家信息网最后更新 2025年01月17日,这篇文章主要介绍"FastJson long溢出问题怎么解决",在日常操作中,相信很多人在FastJson long溢出问题怎么解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对
千家信息网最后更新 2025年01月17日FastJson long溢出问题怎么解决

这篇文章主要介绍"FastJson long溢出问题怎么解决",在日常操作中,相信很多人在FastJson long溢出问题怎么解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"FastJson long溢出问题怎么解决"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    背景

    严选项目中早期(2015年底)接入了 FastJson(版本 1.1.48.android),随着业务发展,个别请求字段数值超出 int 范围,暴露了 FastJson 当前版本的这个溢出问题。

    问题1. 对象转 json 字符串错误

    在网络请求 response body 数据解析中,为了将 json 数据映射到对象上,调用了 JSON.toJSONString() 方法,而这里的数据处理出现了 long 数据溢出,数据发生错误

    Object result = isArray ?        JSON.parseArray(jsonObj.getJSONArray("data").toJSONString(), modelCls) :        jsonObj.getObject("data", modelCls);parseResult.setResult(result);

    数组对象映射代码看着有点怪,性能会有点浪费,因为涉及接口不多也没想到有更好的映射方式,就没改,轻喷。

    问题2. 对象转字节数组错误

    网络请求 request body 转字节数组过程,调用了 JSON.toJSONBytes 接口,而当 mBodyMap 中存在 long 字段时发生了溢出。

    @Overridepublic byte[] getContenteAsBytes() {    //防止重复转换    if (mBody == null && mBodyMap.size() != 0) {        mBody = JSON.toJSONBytes(mBodyMap);    }    return mBody;}//mBodyMap 数据内容Map mBodyMap = new HashMap<>();mBodyMap.put("shipAddressId", 117645003002L);...InvoiceSubmitVO submit = new InvoiceSubmitVO();submit.shipAddressId = 117645003002L;mBodyMap.put("invoiceSubmite", submit);//后端接收数据内容{    "invoiceSubmite":{        "shipAddressId": 117645003002,        ...    },    "shipAddressId": 1680886010,        ...}

    同样的 2 个 long 字段 shipAddressId,一个能正常解析,一个发生了溢出。

    1 问题解析

    编写测试代码:

    public static void test() {    JSONObject jsonObj = new JSONObject();    jsonObj.put("_int", 100);    jsonObj.put("_long", 1234567890120L);    jsonObj.put("_string", "string");    String json0 = JSON.toJSONString(jsonObj);    Log.i("TEST0", "json0 = " + json0);            TestModel model = new TestModel();    String json1 = JSON.toJSONString(model);    Log.i("TEST1", "json1 = " + json1);}private static class TestModel {    public int _int = 100;    public long _long = 1234567890120L;    public String _string = "string";}

    内容输出

    I/TEST0: json0 = {"_int":100,"_long":1912276168,"_string":"string"}

    I/TEST1: json1 = {"_int":100,"_long":1234567890120,"_string":"string"}

    可以找到规律 map 中 long value 解析时,发生了溢出;而类对象中的 long 字段解析正常。

    查看源码:

    // JSON.javapublic String toJSONString() {    SerializeWriter out = new SerializeWriter((Writer)null, DEFAULT_GENERATE_FEATURE, SerializerFeature.EMPTY);    String var2;    try {        (new JSONSerializer(out, SerializeConfig.globalInstance)).write(this);        var2 = out.toString();    } finally {        out.close();    }    return var2;}    public static final String toJSONString(Object object, SerializerFeature... features) {    SerializeWriter out = new SerializeWriter((Writer)null, DEFAULT_GENERATE_FEATURE, features);    String var4;    try {        JSONSerializer serializer = new JSONSerializer(out, SerializeConfig.globalInstance);        serializer.write(object);        var4 = out.toString();    } finally {        out.close();    }    return var4;}

    可以看到,最终调用的都是 JSONSerializer.write 方法

    //JSONSerializer.javapublic final void write(Object object) {    ...    ObjectSerializer writer = this.getObjectWriter(clazz);    ...}public ObjectSerializer getObjectWriter(Class clazz) {    ObjectSerializer writer = (ObjectSerializer)this.config.get(clazz);    if (writer == null) {        if(Map.class.isAssignableFrom(clazz)) {            this.config.put(clazz, MapCodec.instance);        }        ...        else {            Class superClass;            if(!clazz.isEnum() && ((superClass = clazz.getSuperclass()) == null || superClass == Object.class || !superClass.isEnum())) {                if(clazz.isArray()) {                    ...                }                ...                else {                    ...                    this.config.put(clazz, this.config.createJavaBeanSerializer(clazz));                }            } else {                ...            }        }        writer = (ObjectSerializer)this.config.get(clazz);    }    return writer;}

    可以看到 Map 对象使用 MapCodec 处理,普通 Class 对象使用 JavaBeanSerializer 处理

    MapCodec 处理序列化写入逻辑:

    Class clazz = value.getClass();if(clazz == preClazz) {    preWriter.write(serializer, value, entryKey, (Type)null);} else {    preClazz = clazz;    preWriter = serializer.getObjectWriter(clazz);    preWriter.write(serializer, value, entryKey, (Type)null);}

    针对 long 字段的序列化类可以查看得到是 IntegerCodec 类

    // SerializeConfig.javapublic SerializeConfig(int tableSize) {    super(tableSize);    ...    this.put(Byte.class, IntegerCodec.instance);    this.put(Short.class, IntegerCodec.instance);    this.put(Integer.class, IntegerCodec.instance);    this.put(Long.class, IntegerCodec.instance);    ...}

    而查看 IntegerCodec 源码就能看到问题原因:由于前面 fieldType 写死 null 传入,导致最后写入都是 out.writeInt(value.intValue()); 出现了溢出。

    \\IntegerCodec.javapublic void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {    SerializeWriter out = serializer.out;    Number value = (Number)object;    if(value == null) {        ...    } else {        if (fieldType != Long.TYPE && fieldType != Long.class) {            out.writeInt(value.intValue());        } else {            out.writeLong(value.longValue());        }    }}

    而当 long 值是一个class 字段时,查看 JavaBeanSerializer.write 方法,确实是被正确写入。

    // JavaBeanSerializer.javapublic void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {    ...    if(valueGot && !propertyValueGot) {        if(fieldClass != Integer.TYPE) {            if(fieldClass == Long.TYPE) {                serializer.out.writeLong(propertyValueLong);            } else if(fieldClass == Boolean.TYPE) {                ...            }        } else if(propertyValueInt == -2147483648) {            ...        }        ...    }    ...}

    2 问题处理

    2.1 使用 ValueFilter 处理

    针对 JSON.toJSONString,可以调用如下方法,并设置 ValueFilter,FastJson 在写入字符串之前会先调用 ValueFilter.process 方法,在该方法中修改 value 的数据类型,从而绕开有 bug 的 IntegerCodec 写入逻辑

    public static final String toJSONString(Object object, SerializeFilter filter, SerializerFeature... features)public interface ValueFilter extends SerializeFilter {    Object process(Object object, String name, Object value);}String json1 = JSON.toJSONString(map, new ValueFilter() {    @Override    public Object process(Object object, String name, Object value) {        if (value instanceof Long) {            return new BigInteger(String.valueOf(value));        }        return value;    }});

    这里修改 long 类型为 BigInteger 类,而值不变,最后将写入操作交给 BigDecimalCodec

    2.2 替换有问题的 IntegerCodec

    查看 SerializeConfig 源码可以发现全部的 ObjectSerializer 子类都集成在 SerializeConfig 中,且内部使用 globalInstance

    public class SerializeConfig extends IdentityHashMap {    public static final SerializeConfig globalInstance = new SerializeConfig();    public ObjectSerializer createJavaBeanSerializer(Class clazz) {        return new JavaBeanSerializer(clazz);    }    public static final SerializeConfig getGlobalInstance() {        return globalInstance;    }    public SerializeConfig() {        this(1024);    }    ...}

    为此可以在 Application 初始化的时候替换 IntegerCodec

    //MyApplication.java@Overridepublic void onCreate() {    super.onCreate();        SerializeConfig.getGlobalInstance().put(Byte.class, NewIntegerCodec.instance);    SerializeConfig.getGlobalInstance().put(Short.class, NewIntegerCodec.instance);    SerializeConfig.getGlobalInstance().put(Integer.class, NewIntegerCodec.instance);    SerializeConfig.getGlobalInstance().put(Long.class, NewIntegerCodec.instance);}

    由于 NewIntegerCodec 用到的 SerializeWriter.features 字段是 protected,为此需要将该类放置在 com.alibaba.fastjson.serializer 包名下

    2.3 升级 FastJson

    现最新版本为 1.1.68.android(2018.07.16),查看 IntegerCodec 类,可以发现 bug 已经修复

    //IntegerCodec.javapublic void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {    ...        if (object instanceof Long) {        out.writeLong(value.longValue());    } else {        out.writeInt(value.intValue());    }        ...}

    综上看起来,最佳方案是升级 FastJson,然而升级过程中还是触发了其他的坑。

    由于 nei 上定义的字段,部分数值变量定义类型为 Number,同样的基本类型,后端字段部分采用了装箱类型,导致了和客户端定义类型不一致(如服务端定义 Integer,客户端定义 int)。

    public static void test() {    String json = "{\"code\":200,\"msg\":\"\",\"data\":{\"_long\":1234567890120,\"_string\":\"string\",\"_int\":null}}";    JSONObject jsonObj = JSONObject.parseObject(json);    AndroidModel AndroidModel = jsonObj.getObject("data", AndroidModel.class);}private static class AndroidModel {    public int _int = 100;    public long _long = 1234567890120L;    public String _string = "string";}

    如上测试代码,在早期版本这么定义并无问题,即便 _int 字段为 null,客户端也能解析成初始值 100。而升级 FastJson 之后,json 字符串解析就会发生崩溃

    //JavaBeanDeserializer.javapublic Object createInstance(Map map, ParserConfig config) //               throws IllegalAccessException,               IllegalArgumentException,               InvocationTargetException {    Object object = null;        if (beanInfo.creatorConstructor == null) {        object = createInstance(null, clazz);                for (Map.Entry entry : map.entrySet()) {            ...            if (method != null) {                Type paramType = method.getGenericParameterTypes()[0];                value = TypeUtils.cast(value, paramType, config);                method.invoke(object, new Object[] { value });            } else {                Field field = fieldDeser.fieldInfo.field;                Type paramType = fieldDeser.fieldInfo.fieldType;                value = TypeUtils.cast(value, paramType, config);                field.set(object, value);            }        }                return object;    }    ...}TypeUtils.java@SuppressWarnings("unchecked")public static final  T cast(Object obj, Type type, ParserConfig mapping) {    if (obj == null) {        return null;    }    ...}

    查看源码可以发现,当 json 字符串中 value 为 null 的时候,TypeUtils.cast 也直接返回 null,而在执行 field.set(object, value); 时,将 null 强行设置给 int 字段,就会发生 IllegalArgumentException 异常。而由于这个异常情况存在,导致客户端无法升级 FastJson

    到此,关于"FastJson long溢出问题怎么解决"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

    问题 字段 数据 对象 方法 类型 处理 升级 字符 字符串 客户 客户端 源码 版本 学习 代码 内容 数组 错误 为此 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 网络安全小贴士内容 数据库表最多有几个主键 学软件开发基础视频教程 服务器上的英文输入法很奇怪 网络技术搭建概念 美国网络安全工程师的普遍工资 网络安全服务占比高的公司 中行软件开发中心好去吗 阿里云服务器立即购买按钮 简易数据库网站 三级网络技术 什么工作 sql数据库显示对象 常用的问卷调查数据库有哪些 泰州应用软件开发成本预算表 仙桃计算机软件开发费用 珠海公司内训软件开发 珠海多媒体博物馆软件开发 网络安全大屏可视化 网络技术咨询是指 个人信息网络安全事件 mysql数据库增删改查的语法 静安区品牌网络技术费用是多少 贵广网络技术支持工资 废旧手机改造成linux服务器 服务器多CPU任务管理器 嘉定区信息网络技术报价 微软服务器安全组在哪里 游戏平台哪个服务器最好 兴安社区网络安全 互联网科技快速发展优美句子
    0