FastJson 调用 toString 时 null 字段不显示

Wed, Jan 26, 2022

该篇文章主要分析了 fastjson 在没有配置策略时,序列化输出是不会显示值为 null 的字段。

前提

  1. 创建 maven 项目
  2. 引入 fastjson , github 地址
<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.79</version>
</dependency>

现象

public class FastJsonDemo {
  public static void main(String[] args) {
   BizParam param = BizParam.builder().name("xxx").build();
   String json = JSON.toJSONString(param, true);
   System.out.println("json:" + json);
  }
}

====
json:{
	"name":"xxx"
}

上述显示会把 null 的字段干掉

源码跟踪

com.alibaba.fastjson.JSON 中最终调用了 toJSONString(java.lang.Object, int, com.alibaba.fastjson.serializer.SerializerFeature...) , 查看实现

    public static String toJSONString(Object object, int defaultFeatures, SerializerFeature... features) {
        SerializeWriter out = new SerializeWriter((Writer) null, defaultFeatures, features);

        try {
            JSONSerializer serializer = new JSONSerializer(out);
            serializer.write(object);
            String outString = out.toString();
            int len = outString.length();
            if (len > 0
                    && outString.charAt(len -1) == '.'
                    && object instanceof Number
                    && !out.isEnabled(SerializerFeature.WriteClassName)) {
                return outString.substring(0, len - 1);
            }
            return outString;
        } finally {
            out.close();
        }
    }

其中调用了 com.alibaba.fastjson.serializer.JSONSerializer#write(java.lang.Object) 方法,该方法中需要的注意是 getObjectWriter 方法,这个方法会调用 com.alibaba.fastjson.serializer.SerializeConfig#getObjectWriter(java.lang.Class<?>, boolean) ,该方法会判断一系列该类的解析类型, 如果第二个参数为 true 则创建 JavaBeanSerializer 的解析器

...
if (create) {
     writer = createJavaBeanSerializer(clazz);
     put(clazz, writer);
}
...

最终调用了 com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer(com.alibaba.fastjson.serializer.SerializeBeanInfo), 通过一系列的 asm 复制判断,最终如果 asm == true 的话,那么调用 createASMSerializer

if (asm) {
    try {
        ObjectSerializer asmSerializer = createASMSerializer(beanInfo);
        if (asmSerializer != null) {
            return asmSerializer;
        }
    } ...
  ...
}

而实际上调用 com.alibaba.fastjson.serializer.ASMSerializerFactory#createJavaBeanSerializer ,创建了一个 JavaBeanSerializer .

因此在方法 com.alibaba.fastjson.serializer.JSONSerializer#write(java.lang.Object) 调用 com.alibaba.fastjson.serializer.ObjectSerializer#write 方法的时候,实际调用了 com.alibaba.fastjson.serializer.JavaBeanSerializer#write(com.alibaba.fastjson.serializer.JSONSerializer, java.lang.Object, java.lang.Object, java.lang.reflect.Type, int, boolean) ,查看此方法源码发现

...
if (propertyValue == null) {
    int serialzeFeatures = fieldInfo.serialzeFeatures;
    JSONField jsonField = fieldInfo.getAnnotation();
    if (beanInfo.jsonType != null) {
        serialzeFeatures |= SerializerFeature.of(beanInfo.jsonType.serialzeFeatures());
    }
    // beanInfo.jsonType
    if (jsonField != null && !"".equals(jsonField.defaultValue())) {
       propertyValue = jsonField.defaultValue();
    } else if (fieldClass == Boolean.class) {
       ...
    } else if (fieldClass == String.class) {

        int defaultMask = SerializerFeature.WriteNullStringAsEmpty.mask;
        final int mask = defaultMask | SerializerFeature.WriteMapNullValue.mask;
        if ((!writeAsArray) && (serialzeFeatures & mask) == 0 && (out.features & mask) == 0) {
           // 进入这里之后跳出循环,不做任何解析
           continue;
        } else if ((serialzeFeatures & defaultMask) != 0) {
            propertyValue = false;
        } else if ((out.features & defaultMask) != 0
                   && (serialzeFeatures & SerializerFeature.WriteMapNullValue.mask) == 0) {
            propertyValue = false;
        }
    }
}
...

这里((!writeAsArray) && (serialzeFeatures & mask) == 0 && (out.features & mask) == 0) 为什么判断为真呢? 首先 第一个判断 !writeAsArray 为真,它的来源为该方法 (JavaBeanSerializer#write) 中的isWriteAsArray(com.alibaba.fastjson.serializer.JSONSerializer, int) 方法,而 beanInfo 在创建时 features 为 0,那么返回就是 false, 那么 !writeAsArray 便为真.

第二个是 serialzeFeatures 是字段的 serialzeFeatures 也为 0 ,那么 == 0 成立.

第三个 out.features & mask, 由于外部传入为 0。 至此水落石出。

如果要显示 null,使用配置 SerializerFeature.WriteMapNullValue 即可, 如下:

public class FastJsonDemo {
    public static void main(String[] args) {
        BizParam param = BizParam.builder().name("xxx").build();
        String json = JSON.toJSONString(param, true);
        System.out.println("json:" + json);
        String json1 = JSON.toJSONString(param, SerializerFeature.WriteMapNullValue);
        System.out.println("json1:" + json1);
    }
}

-------
json:{
	"name":"xxx"
}
json1:{"name":"xxx","userId":null}

经过调试后调用 com.alibaba.fastjson.serializer.ObjectSerializer#write 时并不会进入 JavaBeanSerializer ,猜想的原因是由于 ASM 字节码修改执行文件有关。这块待深入理解。

其他配置参见 com.alibaba.fastjson.serializer.SerializerFeature

推荐阅读: