文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

FastJson long溢出问题怎么解决

2023-06-26 06:13

关注

这篇文章主要介绍“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<String, Object> 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<ObjectSerializer> {    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<String, Object> map, ParserConfig config) //               throws IllegalAccessException,               IllegalArgumentException,               InvocationTargetException {    Object object = null;        if (beanInfo.creatorConstructor == null) {        object = createInstance(null, clazz);                for (Map.Entry<String, Object> 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> 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溢出问题怎么解决”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯