首页 > 作文

关于FastJson long 溢出问题的小结

更新时间:2023-04-04 14:18:19 阅读: 评论:0

目录
背景问题1. 对象转 json 字符串错误问题2. 对象转字节数组错误1 问题解析2 问题处理2.1 使用 valuefilter 处理2.2 替换有问题的 integercodec2.3 升级 fastjson3 小结

背景

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

当做总结,希望其他团队可以趁早规避这个坑

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

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

object result = isarray ?    json.pararray(jsonobj.getjsonarray("data").tojsonstring(), modelcls) :    jsonobj.getobject("data", modelcls);parresult.tresult(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: jso休闲西装搭配n0 = {“_int”:100,”_long”:1912276168,”_string”:”string”}

i/test1: json1 = {“_int”:100,”_long”:1234567890120,”_string”:”string”}

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

查看源码:

// json.javapublic string tojsonstring() {  rializewriter out = new rializewriter((writer)null, default_generate_feature, rializerfeature.empty);  string var2;  try {    (new jsonrializer(out, rializeconfig.globalinstance)).write(this);    var2 = out.tostring();  } finally {    out.clo();  }  return var2;} public static final string tojsonstring(object object, rializerfeature... features) {  rializewriter out = new rializewriter((writer)null, default_generate_feature, features);  string var4;  try {    jsonrializer rializer = new jsonrializer(out, rializeconfig.globalinstance);    rializer.write(object);    var4 = out.tostring();  } finally {    out.clo();  }  return var4;}

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

//jsonrializer.javapublic final void write(object object) {  ...  objectrializer writer = this.getobjectwriter(clazz);  ...}public objectrializer getobjectwriter(class<?> clazz) {  objectrializer writer = (objectrializer)this.config.get(clazz);  if (writer == null) {    if(map.class.isassignablefrom(clazz)) {      this.config.put(clazz, mapcodec.instance);    }    ...    el {      class superclass;      if(!clazz.inum() && ((superclass = clazz.getsuperclass()) == null || superclass == object.class || !superclass.inum())) {        if(clazz.isarray()) {          ...        }        ...        el {          ...          this.config.put(clazz, this.config.createjavabeanrializer(clazz));        }      } el {        ...      }    }    writer = (objectrializer)this.config.get(clazz);  }  return writer;}

可以看到 map 对象使用 mapcodec 处理,普通 class 对象使用 javabeanrializer 处理

mapcodec 处理序列化写入逻辑:

class<?> clazz = value.getclass();i圆明园毁灭前的资料f(clazz == preclazz) {  prewriter.write(rializer, value, entrykey, (type)null);} el {  preclazz = clazz;  prewriter = rializer.getobjectwriter(clazz);  prewriter.write(rializer, value, entrykey, (type)null);}

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

// rializeconfig.javapublic rializeconfig(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(jsonrializer rializer, object object, object fieldname, type fieldtype) throws ioexception {  rializewriter out = rializer.out;  number value = (number)object;  if(value == null) {    ...  } el {    if (fieldtype != long.type && fieldtype != long.class) {      out.writeint(value.intvalue());    } el {      out.writelong(value.longvalue());    }  }}

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

// javabeanrializer.javapublic void write(jsonrializer rializer, object object, object fieldname, type fieldtype) throws ioexception {  ...  if(valuegot && !propertyvaluegot) {    if(fieldclass != integer.type) {      if(fieldclass == long.type) {        rializer.out.writelong(propertyvaluelong);      } el if(fieldclass == boolean.type) {        ...      }    } el if(propertyvalueint == -2147483648) {      ...    }    ...  }  ...}

2 问题处理

2.1 使用 valuefilter 处理

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

public static final string tojsonstring(object object, rializefilter filter, rializerfeature... features)public interface valuefilter extends rializefilter {  object process(object object, string name, object value);}string json1 = json.tojsonstring(map, new valuefilter() {  @override  have和has的用法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

查看 rializeconfig 源码可以发现全部的 objectrializer 子类都集成在 rializeconfig 中,且内部使用 globalinstance

public class rializeconfig extends identityhashmap<objectrializer> {  public static final rializeconfig globalinstance = new rializeconfig();  public objectrializer createjavabeanrializer(class<?> clazz) {    return new javabeanrializer(clazz);  }  public static final rializeconfig getglobalinstance() {    return globalinstance;  }  public rializeconfig() {    this(1024);  }  ...}

为此可以在 application 初始化的时候替换 integercodec

//myapplication.java@overridepublic void oncreate() {  super.oncreate();   rializeconfig.getglobalinstance().put(byte.class, newintegercodec.instance);  rializ励志流行歌曲econfig.getglobalinstance().put(short.class, newintegercodec.instance);  rializeconfig.getglobalinstance().put(integer.class, newintegercodec.instance);  rializeconfig.getglobalinstance().put(long.class, newintegercodec.instance);}

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

2.3 升级 fastjson

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

//integercodec.javapublic void write(jsonrializer rializer, object object, object fieldname, type fieldtype) throws ioexception {  ...   if (object instanceof long) {    out.writelong(value.longvalue());  } el {    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.parobject(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 字符串解析就会发生崩溃

//javabeanderializer.javapublic object createinstance(map<string, object> map, parrconfig config) //       throws illegalaccesxception,       illegalargumentexception,       invocationtargetexception {  object object = null;   if (beaninfo.creatorconstructor == null) {    object = createinstance(null, clazz);       for (map.entry<string, object> entry : map.entryt()) {      ...      if (method != null) {        type paramtype = method.getgenericparametertypes()[0];        value = typeutils.cast(value, paramtype, config);        method.invoke(object, new object[] { value });      } el {        field field = fieldder.fieldinfo.field;        type paramtype = fieldder.fieldinfo.fieldtype;        value = typeutils.cast(value, paramtype, config);        field.t(object, value);      }    }       return object;  }  ...}typeutils.java@suppresswarnings("unchecked")public static final <t> t cast(object obj, type type, parrconfig mapping) {  if (obj == null) {    return null;  }  ...}

查看源码可以发现,当 json 字符串中 value 为 null 的时候,typeutils.cast 也直接返回 null,而在执行 field.t(object, value); 时,将 null 强行设置给 int 字段,就会发生 illegalargumentexception 异常。

而由于这个异常情况存在,导致客户端无法升级 fastjson

3 小结

以上便是我们严选最近碰到的问题,即便是 fastjson 这么有名的库,也存在这么明显debug,感觉有些吃惊。然而由于服务端和客户端 nei 上定义的字段类型不一致(装箱和拆箱类型),而导致 android 不能升级 fastjson,也警示了我们在 2 端接口协议等方面,必须要保持一致。

此外,上述解决方案 1、2,也仅仅解决了 json 序列化问题,而反序列化如 defaultjsonparr 并不生效。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持www.887551.com。

本文发布于:2023-04-04 14:18:17,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/3727fa8e12af96a8e15193ab6fe884c9.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:关于FastJson long 溢出问题的小结.doc

本文 PDF 下载地址:关于FastJson long 溢出问题的小结.pdf

标签:字段   对象   定义   类型
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图