天天看點

用了幾年的 Fastjson,我最終替換成了Jackson!

為什麼要替換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

用了幾年的 Fastjson,我最終替換成了Jackson!

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!
用了幾年的 Fastjson,我最終替換成了Jackson!

反序列化fastjson和jackson的特性TestCase見DeserializationUseJacksonReplaceFastJsonTest.java

Serialization

fastjson将Java Bean序列化成json字元串通常也是使用com.alibaba.fastjson.JSON的靜态方法(JSONObject和JSONArray的靜态方法也是來自于JSON),常用的有以下幾個API:

用了幾年的 Fastjson,我最終替換成了Jackson!

defaultFeatures

DEFAULT_GENERATE_FEATURE

JSON.DEFAULT_GENERATE_FEATURE

用了幾年的 Fastjson,我最終替換成了Jackson!

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

用了幾年的 Fastjson,我最終替換成了Jackson!
用了幾年的 Fastjson,我最終替換成了Jackson!
用了幾年的 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。