fastjson改造,fastjson转jackson(真实项目案例,主要提供思路)

背景

  1. fastjson太过于侧重性能,对于部分高级特性支持不够,而且部分自定义特性完全偏离了json和js规范导致和其他框架不兼容;
  2. fastjson文档缺失较多,部分Feature甚至没有文档,而且代码缺少注释较为晦涩;
  3. fastjson的CVE bug监测较弱,很多CVE数据库网站上有关fastjson的CVE寥寥无几,例如近期的AutoType导致的高危漏洞,虽然和Jackson的PolymorphicDeserialization是同样的bug,但是CVE网站上几乎没有fastjson的bug报告。

jackson和gson的使用数量遥遥领先,但是jackson有更多现成的类库兼容支持例如jackson-datatype-commons-lang3,以及更丰富的输出数据格式支持例如jackson-dataformat-yaml,而且spring框架默认使用jackson,因此最终我选择使用jackson。(其实是公司要求)

声明一下,本文主要讲述改造思路,并没有粘贴源码(怕),但是保证看完之后能对fastjson和jackson的基本结构有更多认识,对fastjson改造有一定的思路。我们组的项目就是这么改的,历史两周,已开发自测完成。

在这里插入图片描述

fastjson和jackson对比

想要改造好,必须先了解二者的基本结构,对两个框架的功能非常了解。接下来先介绍一下fastjson和jackson。

fastjson的基本结构

目前项目中大量的代码均使用了fastjson和json-lib两种框架(主要是fastjson),总计超过200个类中使用了这两种框架,且fastjson的代码api和jackson的相差较大,逐一改造十分困难,改造量巨大,想要改造的话需要先熟悉一下这两种框架,下面是二者的对比。

项目中主要使用的是fastjson的三个类或接口,分别是JSON,JSONObject,JSONObject我们先来看一下fastjson的基本结构。

在这里插入图片描述

在这里插入图片描述

不难发现,JSON作为JSONObjectJSONArray的父类,包含了绝大多数JSONObjectJSONArray的功能方法,包括最常用的toJSONStringparseObjectparseArray,主要用来进行 json字符串具体的业务对象json对象(即框架定义的包装Json的对象,比如JSONObject和JSONArray)之间的转化。

JSONObjectJSONArray主要的作用是用来存储数据。JSONObject用来存储对象类型的json,并且实现了Map<String,Object>接口,并有一个 Map<String,Object> 类型的属性来存储数据。JSONArray用来存储数组类型的json,实现了List<Object>接口,有一个List<Object> 类型的属性来存储数据。

fastjson的结构还是非常简单的,总结一下,JSON集成了所有功能型的方法。JSONObjectJSONArray 都实现了JSON 接口,分别用来存储对象和数组。

简单的结构应该是fastjson快速的原因之一,或许也是频繁发生安全漏洞的原因。

jackson的基本结构

而jackson的结构就要复杂很多了。

jackson同样有包装json对象和json数组的类,分别为ObjectNodeArrayNode,分别和fastjson的JSONObjectJSONArray对应。ObjectNodeArrayNode同样有一个共同的父类 “JsonNode”,不过不同的是,这个JsonNode并不像fastjson的JSON一样,他并不包含功能类方法,只包含一些基本的操作数据的方法,比如isEmptyisObjectasText 这种方法。并没有将 json字符串与具体对象与json对象之间转化的功能。

那么jackson框架如何进行转化操作的呢?他是通过一个叫做ObjectMapper的类进行的格式转化的功能,而且方法名和fastjson相差确实很多。

如果只是这样的话,看起来差距也没有这么大,应该比较容易就能进行格式转化才对,但是最大的问题并不是这里。
最大的问题是fastjson和jackson的数据存储逻辑也有很大的不同。

首先就是jackson的ObjectNodeArrayNode并没有实现Map接口和List接口,意味着在使用fastjson时直接用JSONObjectJSONArray作为MapList进行参数传递是不行的。

其次,虽然两者都使用Map或者List来存储数据,但是两者Map或者List中可存储的数据类型不同,即Map和List的泛型参数不同,比如说,前面提到过,fastjson的JSONObject使用 Map<String,Object> 进行存储对象的键值对,JSONArray使用List<Object>来存储数组元素。而jackson的ObjectNode使用Map<String,JsonNode>来存储数据,ArrayNode使用List<JsonNode> 进行存储。这就意味着在fastjson中,对象键值对的value可以是任何类型,而ObjectNode的value只能是JsonNode,任何想要存进来的数据都需要转化成JsonNode才行。数组也是同理。

由于jackson对于存储到ObjectNodeArrayNode中数据的类型进行了类型的限制。所以,所有基本类型和String类型在jackson都有对应的 JsonNode 的实现类。比如String的实现类 TextNode,boolean的实现类 BooleanNode等,甚至连null值都有对应的实现类 NullNode

总结一下,jackson 对于所有基本类型和对象都有包装类,包括存储对象的ObjectNode,存储数组的ArrayNode,存储整形数字的IntNode,存储String的TextNode等。而且ObjectNode中用来存储键值对的Map,其value只能是JsonNode或其实现类。ArrayNode中数组的元素也只能存储JsonNode或其实现类。并且他们两个都没有实现Map接口。ObjectMapper是用来进行格式转化的工具类。下面是对应的一些类图。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

改造思路

介绍完两者的不同,接下来介绍一下到底如何改造,具体的改造思路如何。即如何重写JSON,JSONObject和JSONArray三个类。

由于项目中使用到JSONObjectJSONArrayJSON等类的次数过多,并且fastjson和jackson的api差距过大,如果想要修改最少的代码,可以重写JSONObjectJSONArrayJSON这三个类,并且 JSONObject继承ObjectNode类,JSONArray继承ArrayNode类,然后按照fastjson框架中对应的方法名实现对应的方法,直接全局替换包名就可以了。

JSON重写

首先需要重写JSON类,这个类主要是作为一个工具类,后面重写的JSONObjectJSONArray都要在方法中调用JSON工具类中的方法。那为什么不像fastjson那样用继承的方式呢?Java只支持单继承,JSONObjectJSONArray已经继承了ObjectNodeArrayNode了。

因为fastjson和jackson默认的序列化规则可能,比如fastjson默认是支持json字符串的字段名使用单引号括起来,jackson不支持,fastjson默认支持json字符串末尾多一个逗号,jackson不支持等。而且这些不同都是可以通过添加配置来修改的,只需要将所需要的配置添加到ObjectMapper这个mapper中即可。所以需要先配置一个默认的ObjectMapper,使其和fastjson的默认序列化规则相同,即下面类中的 OBJECT_MAPPER常量,并用createMapper配置了一些序列化和反序列化规则,至于最后的filter到后面再说。

配置好默认的mapper了,就可以开始重写fastjson中的方法了,JSON的主要功能是进行数据之间的格式转化,即json字符串、具体的业务对象、json对象(JsonNode及其实现类)这三种数据之间的转化,其中json字符串和具体业务对象之间的转化比较简单,ObjectMapper中都有相应的方法。

而涉及到转化为JsonNode等json对象时会比较复杂。总共包括两种情况,即将json字符串转化为json对象和将业务对象转化为json对象。

这里以将json字符串转化为json对象举例,用ObjectMapper的默认方法最多将json字符串转化为ObjectNode或者ArrayNode等实现类,怎么将ObjectNode或者ArrayNode再转化为JSONObjectJSONArray呢?

其实,真实存储数据的是ObjectNode中的mapArrayNode中的List,我们只需要将maplist中的数据转移过去就好。如果要将ObjectNode转化为JSONObject的话,只要new出一个JSONObject来,将ObjectNode中的map取出并放到新创建的JSONObject中即可,ArrayNode也是同理。这里重写了一个JSONObject的构造方法,接收的参数是ObjectNode,方法中遍历ObjectNodemap的键值对然后插入到新的JSONObject中。注意,这里新创建的JSONObjectmap和原来ObjectNode中的map不是一个,并且map中的value也不是同一个对象,只是值相同而已,原因是 Mapput方法应该就是创建了一个新的对象然后再把值放进去(值拷贝)。

这里需要注意,如果json字符串中的json对象有多层,也就是对象中有对象或者对象中有数组,那么为了使每一层ArrayNodeObjectNode都能被转化为JSONArrayJSONObject的话,需要递归进行上述操作。也就是这里的fromNode方法。

public static JsonNode fromNode(JsonNode jsonNode){  
    if (jsonNode != null) {  
       try {  
          if(jsonNode.isObject()){  
             JSONObject jsonObject = new JSONObject((ObjectNode) jsonNode);  
             Iterator<Map.Entry<String, JsonNode>> fields = jsonObject.fields();  
             while(fields.hasNext()){  
                Map.Entry<String, JsonNode> next = fields.next();  
                String key = next.getKey();  
                JsonNode value = next.getValue();  
                value = fromNode(value);  
                jsonObject.put(key,value);  
             }  
             return jsonObject;  
          }else if(jsonNode.isArray()){  
             JSONArray jsonArray = new JSONArray((ArrayNode) jsonNode);  
             for(int i=0;i<jsonArray.size();i++){  
                JsonNode value = jsonArray.get(i);  
                value = fromNode(value);  
                jsonArray.set(i,value);  
             }  
             return jsonArray;  
          }else{  
             return jsonNode;  
          }       } catch (Exception e) {  
          logger.error("对象转化为JsonNode失败");  
          throw new MdmRuntimeException("对象转化为JsonNode失败", e);  
       }  
    }  
    return null;  
}

经过上述操作,已经完成了fastjson中JSON的大部分功能,即进行数据格式的转化,剩下就是对JSONObjectJSONArray进行重写了。

JSONObject和JSONArray重写

JSONObjectJSONArray的大部分的方法就是对内部的数据进行获取或者添加,大部分是比较容易编写的,需要注意的包括几点:

  1. 自定义的JSONObject的value只能是JsonNode,而fastjson的JSONObject可以的value可以是任何对象。这点在上文提到过,这就要求在往里塞数据时需要多转化一步,包括null值。JSONArray也是同理。

  2. JSONObjectgetJSONObject方法使用了强转的方式,将拿到的ObjectNode直接强转成JSONObject后返回,因为前面的反序列化方法等已经将内部所有层的ObjectNode全都转化为JSONObject了,而所有的JSONObject都是通过反序列化得到的,所以可以直接强转。
    这里需要注意的是,不能通过get到ObjectNode后通过上面讲述的fromNode方法进行转化,上文提到过,转化后的JSONObject和之前的ObjectNode中的map不是一个,里面的value也不是同一个对象。

这样就完成了所有的基本功能,即数据之间的转化和对象的基本操作。其余就剩一些特殊情况的处理,比如特殊的序列化规则和反序列化的规则(比如字段过滤等)、对JSONObjectJSONArray的遍历(fastjson的实现了Map或者List,能使用Map和List的遍历方式,jackson的不行),将JSONObject当作map进行参数传递等等,这些特殊情况都需要单独处理了。

特别注意

修改完成之后两条常见的问题。

  1. 如果将JSONObject或者JSONArray直接作为controller接受的参数,或者接受的对象中有JSONObjectJSONArray作为属性,需要注意,会产生类型转化错误,会报错”不能将ObjectNode转化为JSONObject“,这时需要编写一个JSONObject的反序列化器,使用前面将ObjectNodeJSONObject转化的方法进行转化,然后将反序列器通过注释(@JsonDeserialize(using = JSONObjectDeserializer.class) 加到JSONObject类和JSONObject属性上。JSONArray同理。

  2. 如果想从JSONObject或者JSONArray中获取字符串类型的元素的话,注意,get拿到的元素为TextNode类型,此时不能调用toString方法,需要调用asText方法,如果使用toString的话,会导致拿到的字符串多一对双引号。

序列化和反序列化规则

Deserialization

fastjson将json字符串反序列化成Java Bean通常使用com.alibaba.fastjson.JSON的静态方法(JSONObjectJSONArray的静态方法也是来自于JSON),常用的有以下几个API:

public static JSONObject parseObject(String text);

public static JSONObject parseObject(String text, Feature... features);

public static <T> T parseObject(String text, Class<T> clazz);

public static <T> T parseObject(String text, Class<T> clazz, Feature... features);

public static <T> T parseObject(String text, TypeReference<T> type, Feature... features);

public static JSONArray parseArray(String text);

public static <T> List<T> parseArray(String text, Class<T> clazz);

从方法入参就能猜到,fastjson在执行反序列化时的Parse行为由com.alibaba.fastjson.parser.Feature指定。研究parseObject的源码后,发现底层最终都是使用的以下方法:

public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) {
   if (input == null) {
       return null;
   }

   // featureValues作为基准解析特性开关值
   // 入参features和featureValues取并集得到最终的解析特性
   if (features != null) {
       for (Feature feature : features) {
           featureValues |= feature.mask;
       }
   }

   DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);

   if (processor != null) {
       if (processor instanceof ExtraTypeProvider) {
           parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
       }

       if (processor instanceof ExtraProcessor) {
           parser.getExtraProcessors().add((ExtraProcessor) processor);
       }

       if (processor instanceof FieldTypeResolver) {
           parser.setFieldTypeResolver((FieldTypeResolver) processor);
       }
   }

   T value = (T) parser.parseObject(clazz, null);

   parser.handleResovleTask(value);

   parser.close();

   return (T) value;

}

通过IDE搜索usage后,发现当没有作为基准解析特性开关的featureValues入参时,都是使用的DEFAULT_PARSE_FEATURE作为基准解析特性开关,以下是JSON.DEFAULT_PARSE_FEATURE的实例化代码:

static {
        int features = 0;
        features |= Feature.AutoCloseSource.getMask();
        features |= Feature.InternFieldNames.getMask();
        features |= Feature.UseBigDecimal.getMask();
        features |= Feature.AllowUnQuotedFieldNames.getMask();
        features |= Feature.AllowSingleQuotes.getMask();
        features |= Feature.AllowArbitraryCommas.getMask();
        features |= Feature.SortFeidFastMatch.getMask();
        features |= Feature.IgnoreNotMatch.getMask();
        DEFAULT_PARSER_FEATURE = features;
}

fastjson还会从环境变量中读取配置来修改DEFAULT_PARSER_FEATURE(虽然很少会有人这么做),但最好还是通过实际运行一下程序来确认你的环境中的实际解析特性开关。

@Test
public void printFastJsonDefaultParserFeature() {
    for (Feature feature : Feature.values()) {
        if (Feature.isEnabled(JSON.DEFAULT_PARSER_FEATURE, feature)) {
            System.out.println(feature);
        }
    }
}

fastjson 和 jackson的反序列化特性对照表

fastjson特性说明fastjson枚举fastjson默认状态jackson枚举jackson默认状态jackson特性说明
Parser close时自动关闭为创建Parser实例而创建的底层InputStream以及Reader等输入流Feature.AutoCloseSource开启JsonParser.Feature.AUTO_CLOSE_SOURCE开启保持开启
允许json字符串中带注释Feature.AllowComment关闭JsonParser.Feature.ALLOW_COMMENTS关闭根据系统的json数据情况开启
允许json字段名不被引号包括起来Feature.AllowUnQuotedFieldNames开启JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES关闭根据系统的json数据情况开启
允许json字段名使用单引号包括起来Feature.AllowSingleQuotes开启JsonParser.Feature.ALLOW_SINGLE_QUOTES关闭根据系统的json数据情况开启
将json字段名作为字面量缓存起来,即fieldName.intern()Feature.InternFieldNames开启--jackson默认使用InternCache缓存了PropertyName
识别ISO8601格式的日期字符串,例如:2018-05-31T19:13:42.000Z2018-05-31T19:13:42.000+07:00Feature.AllowISO8601DateFormat关闭--jackson默认支持ISO8601格式日期字符串的解析,并且也可以通过ObjectMapper.setDateFormat指定解析格式
忽略json中包含的连续的多个逗号,非标准特性Feature.AllowArbitraryCommas关闭--jackson不支持该特性,且该特性是非标准特性,因此可以忽略
将json中的浮点数解析成BigDecimal对象,禁用后会解析成Double对象Feature.UseBigDecimal开启DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS关闭建议开启
解析时忽略未知的字段继续完成解析Feature.IgnoreNotMatch开启DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES开启jackson默认开启遇到未知属性需要抛异常,因此如要和fastjson保持一致则需要关闭该特性
如果你用fastjson序列化的文本,输出的结果是按照fieldName排序输出的,parser时也能利用这个顺序进行优化读取。这种情况下,parser能够获得非常好的性能Feature.SortFeidFastMatch关闭--fastjson内部处理逻辑,jackson不支持该特性,不影响功能
禁用ASMFeature.DisableASM关闭--fastjson内部处理逻辑,jackson不支持该特性,不影响功能
禁用循环引用检测Feature.DisableCircularReferenceDetect关闭--fastjson内部处理逻辑,jackson不支持该特性,不影响功能
对于没有值的字符串属性设置为空串Feature.InitStringFieldAsEmpty关闭--jackson不支持该特性,但是可以通过@JsonSetternulls()contentNulls()分别设置Bean以及Array/Collection的元素对null的处理方式。例如Nulls.AS_EMPTY就会将null设置为JsonDeserializer.getEmptyValue
非标准特性,允许将数组按照字段顺序解析成Java Bean,例如"[1001,\"xx\",33]"可以等价为"{\"id\": 10001, \"name\": \"xx\", \"age\": 33}"Feature.SupportArrayToBean关闭--非标准特性,且使用场景较少,jackson不支持该特性
解析后属性保持原来的顺序Feature.OrderedField关闭---
禁用特殊字符检查Feature.DisableSpecialKeyDetect关闭---
使用对象数组而不是集合Feature.UseObjectArray关闭DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY关闭保持关闭
支持解析没有setter方法的非public属性Feature.SupportNonPublicField关闭--jaskson可以通过ObjectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)来达到相同的目的
禁用fastjson的AUTOTYPE特性,即不按照json字符串中的@type自动选择反序列化类Feature.IgnoreAutoType关闭--jackson的PolymorphicDeserialization默认是支持Object.classabstract classesinterfaces属性的AUTO Type,但是该特性容易导致安全漏洞,强烈建议使用ObjectMapper.disableDefaultTyping()设置为只允许@JsonTypeInfo生效
禁用属性智能匹配,例如下划线自动匹配驼峰等Feature.DisableFieldSmartMatch关闭--jackson可以通过ObjectMapper.setPropertyNamingStrategy()达到相同的目的,但这种是针对一个json串的统一策略,如果要在一个json串中使用不同的策略则可以使用@JsonProperty.value()指定字段名
启用fastjson的autotype功能,即根据json字符串中的@type自动选择反序列化的类Feature.SupportAutoType关闭ObjectMapper.DefaultTyping.*开启jackson的PolymorphicDeserialization支持不同级别的AUTO TYPE,但是这个功能容易导致安全漏洞,强烈建议使用ObjectMapper.disableDefaultTyping()设置为只允许@JsonTypeInfo生效
解析时将未用引号包含的json字段名作为String类型存储,否则只能用原始类型获取key的值。例如String text="{123:\"abc\"}"在启用了NonStringKeyAsString后可以通过JSON.parseObject(text).getString("123")的方式获取到"abc",而在不启用NonStringKeyAsString时,JSON.parseObject(text).getString("123")只能得到null,必须通过JSON.parseObject(text).get(123)的方式才能获取到"abc"Feature.NonStringKeyAsString关闭--非标准特性,jackson并不支持
自定义"{\"key\":value}"解析成Map实例,否则解析为JSONObjectFeature.CustomMapDeserializer关闭--jackson没有相应的全局特性,但是可以通过TypeReference达到相同的效果
枚举未匹配到时抛出异常,否则解析为nullFeature.ErrorOnEnumNotMatch关闭DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL关闭fastjson默认解析为null,jackson则相反,默认会抛异常,建议采用jackson默认行为

Serialization

fastjson将Java Bean序列化成json字符串通常也是使用com.alibaba.fastjson.JSON的静态方法(JSONObjectJSONArray的静态方法也是来自于JSON),常用的有以下几个API:

public static String toJSONString(Object object);

public static String toJSONString(Object object, SerializerFeature... features);

public static String toJSONStringWithDateFormat(Object object, String dateFormat, SerializerFeature... features);

public static String toJSONString(Object object, boolean prettyFormat);

public static void writeJSONString(Writer writer, Object object, SerializerFeature... features);

从方法入参也能看出,在序列化时,fastjson的特性由SerializerFeature控制,研究toJSONString的源码后,发现最终都会调用以下方法:

 public static String toJSONString(Object object, SerializeConfig config, SerializeFilter[] filters, String dateFormat, int defaultFeatures, SerializerFeature... features) {
   SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);

   try {
       JSONSerializer serializer = new JSONSerializer(out, config);

       if (dateFormat != null && dateFormat.length() != 0) {
           serializer.setDateFormat(dateFormat);
           serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
       }

       if (filters != null) {
           for (SerializeFilter filter : filters) {
               serializer.addFilter(filter);
           }
       }

       serializer.write(object);

       return out.toString();
   } finally {
       out.close();
   }
}

通过IDE搜索usage后,发现当没有作为基准解析特性开关的defaultFeatures入参时,都是使用的DEFAULT_GENERATE_FEATURE作为基准解析特性开关,以下是JSON.DEFAULT_GENERATE_FEATURE的实例化代码:

static {
    int features = 0;
    features |= SerializerFeature.QuoteFieldNames.getMask();
    features |= SerializerFeature.SkipTransientField.getMask();
    features |= SerializerFeature.WriteEnumUsingName.getMask();
    features |= SerializerFeature.SortField.getMask();

    DEFAULT_GENERATE_FEATURE = features;

    config(IOUtils.DEFAULT_PROPERTIES);
}

fastjson还会从环境变量中读取配置来修改DEFAULT_GENERATE_FEATURE(虽然很少会有人这么做),但最好还是通过实际运行一下程序来确认你的环境中的实际解析特性开关。

@Test
public void printFastJsonDefaultGenerateFeature() {
    for (SerializerFeature feature : SerializerFeature.values()) {
        if (SerializerFeature.isEnabled(JSON.DEFAULT_GENERATE_FEATURE, feature)) {
            System.out.println(feature);
        }
    }
}

fastjson 和 jackson的序列化特性对照表

fastjson特性说明fastjson枚举fastjson默认状态jackson枚举jackson默认状态jackson特性说明
输出key时是否使用双引号SerializerFeature.QuoteFieldNames开启JsonGenerator.Feature.QUOTE_FIELD_NAMES开启保持开启
序列化时使用单引号,而不是使用双引号SerializerFeature.UseSingleQuotes关闭--jackson不支持该特性
序列化时,value为null的key或field也输出SerializerFeature.WriteMapNullValue关闭JsonInclude.Include.ALWAYS开启建议按需选择。注意SerializationFeature.WRITE_NULL_MAP_VALUES从2.9已废弃,且会被JsonInclude.Include给覆盖
序列化枚举时使用枚举类型的toString()方法,和SerializerFeature.WriteEnumUsingName互斥SerializerFeature.WriteEnumUsingToString关闭SerializationFeature.WRITE_ENUMS_USING_TO_STRING关闭建议关闭,或者和反序列化的DeserializationFeature.READ_ENUMS_USING_TO_STRING保持一致
序列化枚举时使用枚举类型的name()方法,和SerializerFeature.WriteEnumUsingToString互斥SerializerFeature.WriteEnumUsingName开启--jackson的默认行为,无需配置
序列化时对Date、Calendar等类型使用ISO8601格式进行格式化,否则以timestamp形式输出Long数字SerializerFeature.UseISO8601DateFormat关闭SerializationFeature.WRITE_DATES_AS_TIMESTAMPS开启jackson和fastjson的默认行为都是将Date数据输出为Long,建议根据不同的场景选择是否需要格式化日期
序列化List类型数据时将null输出为"[]"SerializerFeature.WriteNullListAsEmpty关闭--可以通过PropertyFilter/SerializerFactory.withSerializerModifier(BeanSerializerModifier)任一一种方式达到相同效果,推荐使用PropertyFilter
序列化String类型的field时将null输出为""SerializerFeature.WriteNullStringAsEmpty关闭--可以通过PropertyFilter/SerializerFactory.withSerializerModifier(BeanSerializerModifier)任一一种方式达到相同效果,推荐使用PropertyFilter
序列化Number类型的field时将null输出为0SerializerFeature.WriteNullNumberAsZero关闭--可以通过PropertyFilter/SerializerFactory.withSerializerModifier(BeanSerializerModifier)任一一种方式达到相同效果,推荐使用PropertyFilter
序列化Boolean类型的field时将null输出为falseSerializerFeature.WriteNullBooleanAsFalse关闭--可以通过PropertyFilter/SerializerFactory.withSerializerModifier(BeanSerializerModifier)任一一种方式达到相同效果,推荐使用PropertyFilter
序列化时忽略transient修饰的fieldSerializerFeature.SkipTransientField开启MapperFeature.PROPAGATE_TRANSIENT_MARKER关闭建议保持关闭,通过@JsonIgnore或者FilterProvider来指定忽略的属性
序列化时,如果未指定order,则将field按照getter方法的字典顺序排序SerializerFeature.SortField开启MapperFeature.SORT_PROPERTIES_ALPHABETICALLY关闭建议关闭,排序会影响序列化性能(fastjson在反序列化时支持按照field顺序读取解析,因此排序后的json串有利于提高fastjson的解析性能,但jackson并没有该特性)
\t做转义输出,已废弃,即使开启也无效SerializerFeature.WriteTabAsSpecial关闭---
格式化json输出SerializerFeature.PrettyFormat关闭SerializationFeature.INDENT_OUTPUT关闭建议保持关闭,格式化可以交给前端完成
序列化时把类型名称写入jsonSerializerFeature.WriteClassName关闭--jackson可以通过@JsonTypeInfo达到类似的效果,参见Jackson Annotation Examples
序列化时消除对同一对象循环引用的问题SerializerFeature.DisableCircularReferenceDetect关闭SerializationFeature.FAIL_ON_SELF_REFERENCES开启保持开启,避免循环引用
对斜杠’/'进行转义SerializerFeature.WriteSlashAsSpecial关闭--jackson可以通过自定义Serializer实现相同效果,按需设置
将中文都会序列化为\uXXXX格式,字节数会多一些,但是能兼容IE 6SerializerFeature.BrowserCompatible关闭--jackson可以通过自定义Serializer实现相同效果,按需设置
全局修改日期格式,默认使用JSON.DEFFAULT_DATE_FORMATSerializerFeature.WriteDateUseDateFormat关闭--jackson可以通过@JsonFormat.pattern()ObjectMapper.setDateFormat()等方式实现相同效果
序列化时不把最外层的类型名称写入jsonSerializerFeature.NotWriteRootClassName关闭--jackson可以通过@JsonRootName达到类似的效果,参见Jackson Annotation Examples
不转义特殊字符,已废弃,即使开启也无效SerializerFeature.DisableCheckSpecialChar关闭---
将Bean序列化时将field值按顺序当成json数组输出,而不是json object,同时不会输出fieldName,例如:{"id":123,"name":"xxx"}会输出成[123,"xxx"]SerializerFeature.BeanToArray关闭--非标准特性,jackson并不支持
序列化Map时将非String类型的key作为String类型输出,例如:{123:231}会输出成{"123":231}SerializerFeature.WriteNonStringKeyAsString关闭--非标准特性,jackson并不支持
序列化Byte、Short、Integer、Long、Float、Double、Boolean及其对应原始类型field时,如果属性值为各自类型的默认值(如0、0F、0L),则不会输出该属性SerializerFeature.NotWriteDefaultValue关闭--非标准特性,jackson并不支持
序列化时将()><以unicode编码输出SerializerFeature.BrowserSecure关闭--jackson可以通过自定义Serializer实现相同效果,按需设置,通常可以交给前端处理
序列化时忽略没有实际属性对应的getter方法SerializerFeature.IgnoreNonFieldGetter关闭---
序列化时把非String类型数据当作String类型输出SerializerFeature.WriteNonStringValueAsString关闭--jackson有一个类似的特性JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS可以将数字作为字符串输出,但没有覆盖所有非String类型
序列化时忽略会抛异常的getter方法SerializerFeature.IgnoreErrorGetter关闭---
序列化时将BigDecimal使用toPlainString()输出SerializerFeature.WriteBigDecimalAsPlain关闭JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN关闭按需开启
序列化时对Map按照Key进行排序SerializerFeature.MapSortField关闭SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS关闭建议关闭,开启会影响性能

convertValue方法

Jackson的 convertValue 方法主常用于在不同类型的Java对象之间进行转换。这个方法属于 ObjectMapper 类,是Jackson库提供的一个非常强大的功能,可以让你轻松地在各种复杂的数据类型之间进行转换,包括从Java对象到Map、从Map到Java对象、甚至是从一种Java对象转换为另一种Java对象。

原理

convertValue 方法的工作原理基于Jackson的序列化和反序列化能力。当你调用这个方法时,Jackson实际上执行了两个步骤:

  1. 序列化:首先,将源对象序列化为中间形式(通常是JSON字符串)。这一步涉及到根据Java对象的类型信息,将其转换为一种中间表示形式,这个过程中会考虑到对象内部的各种注解、类型信息等,以确保转换的准确性。
  2. 反序列化:然后,将这个中间形式(JSON字符串)反序列化为目标类型的对象。在这一步,Jackson根据目标类型的信息,将中间形式转换为最终的Java对象,这个过程同样会考虑到目标对象类型的注解、构造函数、设值方法等信息,以确保对象能正确并完整地被构建。

使用场景

convertValue 方法非常适合在以下场景中使用:

  • 当你需要将一个复杂的Java对象转换为一个Map,以便于访问其内部属性时。
  • 当你需要将一个Map转换为一个具体的Java对象时,这在处理动态数据或者不太规范的JSON数据时非常有用。
  • 当你需要将一个Java对象转换为另一个Java对象时,特别是这两个对象有着相似的结构但不完全相同,或者它们来自不同的类层次结构。

注意事项

虽然 convertValue 方法非常强大和灵活,但在使用时也需要注意以下几点:

  • 性能考虑:由于转换过程实际上涉及到了序列化和反序列化,所以在性能敏感的场景下要谨慎使用。
  • 类型安全:由于 convertValue 方法在编译时不会检查类型安全,因此在运行时可能会遇到类型不匹配的问题。使用时应确保目标类型与源对象是兼容的。
  • 数据丢失:在从一个类型转换为另一个类型的过程中,如果两个类型不是完全匹配的,可能会发生数据丢失的情况。因此,确保目标类型能够承载源对象的所有重要信息是非常关键的。
  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ponymate

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值