如何在 Jackson 中实现 Set 的空值与空集合双向转换

本文介绍如何通过自定义序列化器和反序列化转换器,使 jackson 将 null set 序列化为 [],并将空数组 [] 反序列化为 null,同时保持非空集合的默认行为。

在 Jackson 中统一处理集合类(如 Set、List)的空值语义是一个常见但易被误解的需求:业务上常需将 null 集合序列化为 JSON 空数组 [](提升 API 兼容性与前端友好性),而将接收到的空数组 [] 显式还原为 null(以区分“未提供”与“明确提供空集合”)。直接继承 StdDeserializer> 并不可行——因为 JsonNode 不支持 asSet(),且标准反序列化流程在进入 deserialize() 前已将 JSON 数组解析为具体集合实例,无法在“集合容器层”拦截。

正确解法是分层协作

  • 序列化层:使用 JsonSerializer 拦截所有 null Collection,强制输出 [];
  • 反序列化层:不重写 Deserializer,而是通过 StdConverter 在反序列化完成后对结果做后处理(将空集合转为 null);
  • 注册机制:利用 JacksonAnnotationIntrospector 全局生效,或通过 @JsonSerialize / @JsonDeserialize 注解按字段定制。

以下为完整可运行示例(基于 Jackson 2.15+ 和 Spring Boot 常用工具类):

// 全局空集合处理器:自动为所有 Collection 类型启用 null ↔ []
public class EmptyAsNullCollectionJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
    @Override
    public Object findNullSerializer(Annotated a) {
        if (Collection.class.isAssignableFrom(a.getRawType())) {
            return NullAsEmptyCollectionJsonSerializer.INSTANCE;
        }
        return super.findNullSerializer(a);
    }

    @Override
    public Object findDeserializationConverter(Annotated a) {
        if (List.class.isAssignableFrom(a.getRawType())) {
            return EmptyListAsNullConverter.INSTANCE;
        }
        if (Set.class.isAssignableFrom(a.getRawType())) {
            return EmptySetAsNullConverter.INSTANCE;
        }
        return super.findDeserializationConverter(a);
    }
}

// 序列化器:所有 null 集合 → []
class NullAsEmptyCollectionJsonSerializer extends JsonSerializer {
    public static final NullAsEmptyCollectionJsonSerializer INSTANCE = new NullAsEmptyCollectionJsonSerializer();
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeStartArray();
        gen.writeEndArray();
    }
}

// 转换器:空 Set → null(反序列化后调用)
class EmptySetAsNullConverter extends StdConverter, Set> {
    public static final EmptySetAsNullConverter INSTANCE = new EmptySetAsNullConverter();
    @Override
    public Set convert(Set value) {
        return CollectionUtils.isEmpty(value) ? null : value;
    }
}

// 同理支持 List(可选)
class EmptyListAsNullConverter extends StdConverter, List> {
    public static final EmptyListAsNullConverter INSTANCE = new EmptyListAsNullConverter();
    @Override
    public List convert(List value) {
        return CollectionUtils.isEmpty(value) ? null : value;
    }
}

使用方式一:全局配置(推荐)

var mapper = JsonMapper.builder()
    .enable(SerializationFeature.INDENT_OUTPUT)
    .annotationIntrospector(new EmptyAsNullCollectionJacksonAnnotationIntrospector())
    .build();

使用方式二:字段级注解(更灵活)

public class Collecti

onsPojo { @JsonSerialize(nullsUsing = NullAsEmptyCollectionJsonSerializer.class) @JsonDeserialize(converter = EmptySetAsNullConverter.class) private Set tags; @JsonSerialize(nullsUsing = NullAsEmptyCollectionJsonSerializer.class) @JsonDeserialize(converter = EmptyListAsNullConverter.class) private List ids; }

效果验证

  • nullSet → "nullSet": [](序列化)→ 反序列化后为 null;
  • emptySet = Set.of() → "emptySet": [] → 反序列化后为 null;
  • setOfOne = Set.of("A") → "setOfOne": ["A"] → 反序列化后仍为 ["A"]。

⚠️ 注意事项

  • StdConverter 是反序列化后置处理,不影响原始解析逻辑,因此安全可靠;
  • findNullSerializer 仅作用于 null 值,不干扰非空集合的默认序列化;
  • 若项目中已存在自定义 AnnotationIntrospector,需合并逻辑而非直接替换;
  • CollectionUtils.isEmpty() 同时兼容 null 和空集合,是安全判断首选。

此方案兼顾简洁性、可维护性与扩展性,无需侵入业务代码,即可统一治理集合空值语义。