为什么要替换fastjson
工程里大量使用了fastjson作为序列化和反序列化框架,甚至ORM在处理部分字段也依赖fastjson进行序列化和反序列化。那么作为大量使用的基础框架,为什么还要进行替换呢?
原因有以下几点:
fastjson太过于侧重性能,对于部分高级特性支持不够,而且部分自定义特性完全偏离了json和js规范导致和其他框架不兼容;
fastjson文档缺失较多,部分Feature甚至没有文档,而且代码缺少注释较为晦涩;
fastjson的CVE bug监测较弱,很多CVE数据库网站上有关fastjson的CVE寥寥无几,例如近期的AutoType导致的高危漏洞,虽然和Jackson的PolymorphicDeserialization是同样的bug,但是CVE网站上几乎没有fastjson的bug报告。
框架选型
参考mvnrepository json libraries,根据流行度排序后前十名框架:
jackson2(com.fasterxml.jackson)
gson
org.json
jackson1(com.codehuas.jackson)
fastjson
cheshire
json-simple
jackson1是已经过时的框架,因此可以忽略,cheshire和json-simple排名尚且不如fastjson,也忽略,剩余jackson2、gson以及org.json,其中org.json的使用量(usage)远小于jackson2(方便起见,下文均以jackson均指代jackson2)和gson,因此org.json也可以排除了。
关于jackson和gson的比较文章有很多,stackoverflow上自行搜索,下面仅推荐几篇blog:
jackson vs gson
JSON in Java
the ultimate json library json-simple vs gson vs jackson vs json
在功能特性支持、稳定性、可扩展性、易用性以及社区活跃度上 jackson 和 gson 差不多,入门教程可以分别参考baeldung jackson系列 以及 baeldung gson系列。但是jackson有更多现成的类库兼容支持例如jackson-datatype-commons-lang3,以及更丰富的输出数据格式支持例如jackson-dataformat-yaml,而且spring框架默认使用jackson,因此最终我选择使用jackson。
PS: Jackson 2.10.0开始尝试基于新的API使用白名单机制来避免RCE漏洞,详见https://github.com/FasterXML/jackson-databind/issues/2195,效果尚待观察。
替换fastjson
fastjson常见的使用场景就是序列化和反序列化,偶尔会有JSONObject和JSONArray实例的相关操作。
以下步骤的源码分析基于以下版本:
fastjson v1.2.60
jackson-core v2.9.9
jackson-annotations v2.9.0
jackson-databind v2.9.9.3
Deserialization
fastjson将json字符串反序列化成Java Bean通常使用com.alibaba.fastjson.JSON的静态方法(JSONObject和JSONArray的静态方法也是来自于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和jackson的特性TestCase见DeserializationUseJacksonReplaceFastJsonTest.java
Serialization
fastjson将Java Bean序列化成json字符串通常也是使用com.alibaba.fastjson.JSON的静态方法(JSONObject和JSONArray的静态方法也是来自于JSON),常用的有以下几个API:
通过IDE搜索usage后,发现当没有作为基准解析特性开关的defaultFeatures
入参时,都是使用的DEFAULT_GENERATE_FEATURE
作为基准解析特性开关,以下是JSON.DEFAULT_GENERATE_FEATURE
的实例化代码:
fastjson 和 jackson的序列化特性对照表
序列化fastjson和jackson的特性TestCase见SerializationUseJacksonReplaceFastJsonTest.java
Annotation
fastjsonzhu相对于jackson来说注解的功能划分的并没有那么细,因此fastjson的一个注解可能等价于jackson多个注解的组合。
@JSONPOJOBuilder
指定反序列化时创建java对象使用的build方法,对应jackson的@JsonPOJOBuilder。
@JSONCreator
指定反序列化时创建java对象使用的构造方法,对应jackson的@JsonCreator。
@JSONField
指定序列化和反序列化field时的行为。反序列化时,等价于@JsonProperty + @JsonDeserialize + @JsonUnwrapped + @JsonFormat+ @JsonAlias;序列化时,等价于@JsonProperty + @JsonSerialize + @JsonUnwrapped + @JsonFormat + @JsonRawValue + @JsonView。
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) public @interface JSONField { // 序列化和反序列化时的字段顺序,等价于jackson的@JsonProperty.index() int ordinal() default 0; // 序列化和反序列化时的字段名称映射,等价于jackson的@JsonProperty.value() String name() default ""; // 序列化和反序列化时的数据格式(日期格式、16进制等等),等价于jackson的@JsonFormat.shape() + @JsonFormat.pattern() String format() default ""; // 字段是否序列化,等价于jackson的@JsonProperty.access() boolean serialize() default true; // 字段是否反序列化,等价于jackson的@JsonProperty.access() boolean deserialize() default true; // 序列化特性,等价于jackson的@JsonProperty.with() SerializerFeature[] serialzeFeatures() default {}; // 反序列化特性,等价于jackson的@JsonFormat.with() Feature[] parseFeatures() default {}; // 对属性进行打标,便于在序列化时进行exclude或include,等价于jackson的@JsonView String label() default ""; // 序列化时将字段内容直接输出,不经过转义,等价于jackson的@JsonRawValue boolean jsonDirect() default false; // 指定序列化时使用的Serializer Class,等价于jackson的@JsonSerialize Class<?> serializeUsing() default Void.class; // 指定反序列化时使用的Deserializer Class,等价于jackson的@JsonDeserialize Class<?> deserializeUsing() default Void.class; // 指定反序列化时使用的字段别名,等价于jackson的@JsonAlias String[] alternateNames() default {}; // 将字段的子属性映射到父节点上,等价于jackson的@JsonUnwrapped boolean unwrapped() default false; // 指定序列化时字段为null时使用的默认值,等价于jackson的@JsonProperty.defaultValue() String defaultValue() default ""; }
unwrapped
的用法可以参考AnnotationUseJacksonReplaceFastJsonTest.java中的testJSONFieldUnwrapped
。
@JSONType
指定序列化和反序列化一个Java Bean时的行为。
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) public @interface JSONType { // 是否使用asm优化,jackson无对应特性 boolean asm() default true; // 序列化和反序列化时的field排序,等价于jackson的@JsonPropertyOrder.value() String[] orders() default {}; // 序列化和反序列化时包含的field,等价于jackson的 String[] includes() default {}; // 序列化和反序列化时忽略的field,等价于jackson的@JsonIgnoreProperties String[] ignores() default {}; // 序列化特性,等价于jackson的@JsonProperty.with() SerializerFeature[] serialzeFeatures() default {}; // 反序列化特性,等价于jackson的@JsonFormat.with() Feature[] parseFeatures() default {}; // 序列化时是否依据field字母顺序排序,等价于jackson的@JsonPropertyOrder.alphabetic() boolean alphabetic() default true; // 反序列化多态类型时,如果根据其他typeName等方式无法找到正确的子类时,默认使用的子类,等价于jackson的@JsonTypeInfo.defaultImpl() Class<?> mappingTo() default Void.class; // 反序列化时指定java bean builder类(必须是@JSONPOJOBuilder注解的类),等价于jackson的@JsonDeserialize.builder() Class<?> builder() default Void.class; // 声明这个类型的别名,反序列化多态类型时使用,等价于jackson的@JsonTypeName String typeName() default ""; // 反序列化某个接口或抽象类或父类的子类时指定根据哪个字段的值和子类的typeName相等来决定具体实现类,等价于jackson的@JsonTypeInfo.use() = Id.CUSTOM + @JsonTypeInfo.property() String typeKey() default ""; // 反序列化某个接口或抽象类或父类的子类时指定可以反序列化的子类类型,等价于jackson的@JsonSubTypes Class<?>[] seeAlso() default{}; // 指定序列化时使用的Serializer Class,等价于jackson的@JsonSerialize Class<?> serializer() default Void.class; // 指定反序列化时使用的Deserializer Class,等价于jackson的@JsonDeserialize Class<?> deserializer() default Void.class; // 序列化时,如果filed是枚举类型,则和普通的java bean一样输出枚举的filed,而不是通常使用的Enum.name()值,jackson没有对应特性 boolean serializeEnumAsJavaBean() default false; // 指定json和Java bean之间的字段名称映射策略,等价于jackson的@JsonNaming PropertyNamingStrategy naming() default PropertyNamingStrategy.CamelCase; // 指定序列化时使用的Serialize filter,等价于jackson的@JsonFilter Class<? extends SerializeFilter>[] serialzeFilters() default {}; }
JSONObject
& JSONArray
首先来看看fastjon中JSONObject
和JSONArray
的源码:
public class JSONObject extends JSON implements Map<String, Object>, Cloneable, Serializable, InvocationHandler { private final Map<String, Object> map; ... } public class JSONArray extends JSON implements List<Object>, Cloneable, RandomAccess, Serializable { private static final long serialVersionUID = 1L; private final List<Object> list; protected transient Object relatedArray; protected transient Type componentType; ... }
从源码就可以发现,JSONObject实际是一个Map,而JSONArray实际是一个List。因此可以将JSONObject类型改为Map,而JSONArray类型改为List。但是这种方式就会导致上层API出现大量修改,因为缺少了JSONObject和JSONArray提供的多种便利的类型转换方法。如果想要暂时保留JSONObject和JSONArray,此时可以采取一种取巧的方法。
暂时保留JSONObject & JSONArray的过渡方法
jackson官方提供了对org.json库的数据类型支持jackson-datatype-json-org,因此可以将com.alibaba.fastjson.JSONObject替换为org.json.JSONObject,com.alibaba.fastjson.JSONArray替换为org.json.JSONArray,这两个类库的对象API大致相同,当然一些细小的改动还是避免不了的。如果想完全不改上层代码,那也可以参考jackson-datatype-json-org和jackson-datatype-json-lib自己实现jackson对fastjson的数据类型的binder。
larva-zhang/jackson-datatype-fastjson欢迎大家使用或提issues。
JSONPath
使用json-path/JsonPath就能轻松替换fastjson的JSONPath,而且功能比fastjson更强大。只需参考JsonProvider SPI使用JacksonJsonProvider替代json-path/JsonPath默认的JsonSmartJsonProvider即可。
自定义扩展
自定义Deserializer
fastjson中实现自定义Deserializer的方法通常是实现ObjectDeserializer接口的deserialze方法
T deserialze(DefaultJSONParser parser, Type type, Object fieldName);
在jackson中实现自定义Serializer的方法则通常是继承StdDeserializer抽象类,重写deserialize方法
public abstract T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException;
自定义Serializer
fastjson中实现自定义Serializer的方法通常是实现ObjectSerializer接口的write方法
void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException;
在jackson中实现自定义Serializer的方法则通常是继承StdSerializer抽象类,重写serialize方法
public abstract void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException;
自定义Serialize Filter
fastjson中提供了6种SerializeFilter,详见fastjson/wiki/SerializeFilter。而在jackson中则是建议继承SimpleBeanPropertyFilter。