欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

fastjson list转json字符串_从fastjson转jackson的血泪史

程序员文章站 2024-03-08 12:22:10
...
最近线上项目频繁报警,运维大神在报警时导出了 java线程堆栈 (jstack工具导出) 。统计分析后,总共运行中2003个线程,有1997个都是在做fastjson的反序列化操作。 (简易分析工具可以使用threaddump-analyzer,github上的一个小项目) fastjson江湖传言是最快的json库。然而这? why?! why?! 经过几个简单测试发现,当序列化和反序列化时,把属性的类型写入到json 时,性能特别特别差!
示例用法:// fastjson 序列化JSON.toJSONString(javaObj, SerializerFeature.WriteClassName);// 类似这样{"@type":"java.util.HashMap","data":{"@type":"...// fastjson 反序列化ParserConfig config = new ParserConfig() ;config.setAutoTypeSupport(true);Map map = JSON.parseObject(json, Map.class, config);
使用相同的特性。在 jackson 框架下,发现性能依然贼好!!最终决定,全面更换 jackson。 由于使用json的地方特别多。大致的更换方案是,完全重写 fastjson 相关的类。尽可能少改动项目现有代码。像 fastjson 中的 JSON, JSONObject, JSONArray 都保持原包名和 类名重写相关使用的方法。 重写完后,以为大功告成,去掉 fastjson的依赖,以为可以上线了!!万万没有想到,这只是万里长征的第一步!!也让我深深体会到 fastjson 的兼容性是有多么多么的好。各种神操作,让你在 jackson 中,都不知道如何改写。也怪我们使用 fastjson的方式太邪乎了!各种非常规使用。 下面讲几个特殊的解析:
  1. javabean的属性是String, 反序列化时,传入这个属性的值是json对象或者json 数组;

  2. 属性是枚举,传入 json 对象;

  3. 属性是对象,传入是String,String是一个json串;

  4. 属性是集合,传入是单个json对象;jackson有配置可以使用configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY,  true)解决问题,但是这样影响处理属性是集合类型,而传入是String的问题。我就没有使用该特性。然后在自定义异常处理中统一处理该问题

    // 问题示例描述public class Abc{    private String attrArr;// 问题1 传入json数组[{"a":"123123"}]    private String attr;// 问题1 传入json对象{"a":"123123"}    private SexEnum sexEnum;// 问题2  传入json对象{"value":1,"label":"男"}      private BBB bbb;  // 问题3 传入字符串"{\"b\":\"abc\"}"     private List bbb; // 问题4 传入json对象{"b":"abc"}     // ...get/set...方法    }public class BBB{    private String b;    // ... get/set...方法    }     在fastjson 中是都可以正常转化
    以上举例的反序列化在jackson中直接报错,在网上未能找到相应问题的解决办法,但是最终通过跟踪报错的堆栈信息,进行调试后,发现可以添加一个异常处理的方法。解决这4个问题代码如下:
    // 对应jackson 的版本 2.11.3, 早期版本可能没有该方法或者参数列表不一致objectMapper.addHandler(new CustomDeserializationProblemHandler());static class CustomDeserializationProblemHandler extends DeserializationProblemHandler {        @Override        public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {            String json = p.getText();            if(targetType.isEnumType() && "{".equals(json)) {                ObjectMapper mapper = (ObjectMapper) p.getCodec();                mapper.readTree(p);// 直接读取一个节点。这样就不会忽略之后的结点解析了,但是并不能正确反序列化枚举,需要通过注解实现                return null;            }            if(targetType.isCollectionLikeType() && "{".equals(json)) {                ObjectMapper mapper = (ObjectMapper) p.getCodec();                Object obj =  mapper.readValue(p, targetType.getContentType());// 把单个结点数据,转化为一个数组对象                List<Object> list = new ArrayList<Object>();                list.add(obj);                return list;            }            if(targetType.getRawClass().equals(String.class) && "{".equals(json)) {                ObjectMapper mapper = (ObjectMapper) p.getCodec();                TreeNode node =  mapper.readTree(p);// 把一个对象json,转成一个字符串,有性能损失                return node.toString();            }            if(targetType.getRawClass().equals(String.class) && "[".equals(json)) {                ObjectMapper mapper = (ObjectMapper) p.getCodec();                TreeNode node =  mapper.readTree(p);// 把一个对象json数组,转成一个字符串,有性能损失                return node.toString();            }            if ((json.startsWith("{") && json.endsWith("}")) || (json.startsWith("[") && json.endsWith("]"))) {                return JSON.parseObject(p.getText(), targetType);            }            return DeserializationProblemHandler.NOT_HANDLED;        }    }
  5. 日期类型反序列化支持多种格式;
    在 fastjson 中,长整型、yyyy-MM-dd、yyyy-MM-dd HH:mm 等格式都能正确转化为日期。但是在 jackson 中默认长整型是可以正常转换的。但是另外两种并不能转换,解决办法如下:

     SimpleModule serializerModule = new SimpleModule("DateSerializer", PackageVersion.VERSION);        serializerModule.addDeserializer(Date.class, new CustomDateDeSerializer());        objectMapper.registerModule(serializerModule);static class CustomDateDeSerializer extends DateDeserializers.DateDeserializer {        private static final long serialVersionUID = 1L;        @Override        public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {            if (p != null) {                String calendatStr = p.getText();                if (calendatStr != null && calendatStr.indexOf("T") < 0 && calendatStr.length() == 19) {                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");                    try {                        return sdf.parse(calendatStr);                    } catch (ParseException e) {                    }                }                if (calendatStr != null && calendatStr.indexOf("T") < 0 && calendatStr.length() == 16) {                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");                    try {                        return sdf.parse(calendatStr);                    } catch (ParseException e) {                    }                }                if (calendatStr != null && calendatStr.length() == 10 && calendatStr.charAt(4) == '-' && calendatStr.charAt(7) == '-') {                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");                    try {                        return sdf.parse(calendatStr);                    } catch (ParseException e) {                    }                }            }            return super.deserialize(p, ctxt);        }    }    
  6. jackson最新版本中做类型保留,这样配置;

    // jackson 2.11.3 配置该信息后,序列化和反序列化都会带有类信息objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,  ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
  7. 枚举类型序列化是以类(即输出属性)形式输出;

    // 在枚举上添加该注解@JsonFormat(shape = Shape.OBJECT)public enum SexEnum{ ..... }

    最后分享一下关于JSON使用的愚见

    1)如果项目中在使用 fastjson 但是没有使用SerializerFeature.WriteClassName特性,没有必要更换新的json 库。但是建议升级最新版本。因为不久前报过多次严重的安全漏洞。

    2)保留类信息的方式,反序列化可能成为黑客攻击的入口。该种方式仅用于项目内部接口的调用。

    3)应规范使用json 的序列化和反序列化。上边提到的第1点。不管在 fastjson还是上边提到的 jackson 解决方案都影响性能。
    4)对于{"action":"add","data":{...大量信息....}}这样json想先确认action值,再对 data做相应类型的解析,建议新建一个类似class SimpleCommand{String action;...get/set...}这样只有 action 一个属性的类进行解析,然后根据 action 值再进行整个json的解析。速度更快一些。

(全文完)

↓↓↓推荐↓↓↓ 

fastjson list转json字符串_从fastjson转jackson的血泪史

一码解千愁