You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson/Kafka序列化问题:JSON非空字段配置失效排查与解决

关于AbstractKafkaJsonSchemaSerializer中NullNode导致校验失败的问题

我在使用io.confluent.kafka.serializers.json.AbstractKafkaJsonSchemaSerializer时,遇到了JSON Schema校验失败的问题。该类的validateJson方法(从第180行开始)代码如下:

protected T validateJson(T object, JsonSchema schema) throws SerializationException {
try {
  JsonNode jsonNode = object instanceof JsonNode ? (JsonNode) object : objectMapper.convertValue(object, JsonNode.class);    
  jsonNode = schema.validate(jsonNode);

执行最后一行schema.validate(jsonNode)时失败,原因是jsonNode中存在深层的NullNode,但我检查objectMapper.getSerializationConfig().getSerializationInclusion()返回的是JsonInclude.Include.NON_NULL。

我有两个问题:

  1. 为什么明明配置了NON_NULL,还是会出现NullNode?
  2. 有没有优雅的方法确保最终的JSON不包含空字段?

问题1:为何会出现NullNode?

  • JsonInclude.Include.NON_NULL是序列化阶段的配置,仅在将Java对象序列化为JSON字符串时生效,会忽略null字段;但objectMapper.convertValue(object, JsonNode.class)属于对象树转换操作,这个过程不会触发序列化的过滤规则。
  • convertValue只是把Java对象转成Jackson的JsonNode树结构,原对象里的null属性会直接对应生成NullNode节点,不会因为NON_NULL配置就跳过这些节点。
  • Schema校验是针对转换后的JsonNode树进行的,自然会检测到这些NullNode。

问题2:优雅的解决方法

方法1:转换后递归过滤NullNode

在生成JsonNode后,手动过滤所有NullNode,如果父节点是ObjectNode且过滤后为空,也可以一并移除,再进行Schema校验:

private JsonNode removeNullNodes(JsonNode node) {
    if (node.isObject()) {
        ObjectNode objectNode = (ObjectNode) node;
        Iterator<Map.Entry<String, JsonNode>> iterator = objectNode.fields();
        while (iterator.hasNext()) {
            Map.Entry<String, JsonNode> entry = iterator.next();
            JsonNode value = entry.getValue();
            if (value.isNull()) {
                iterator.remove();
            } else {
                removeNullNodes(value);
                // 可选:移除处理后为空的子对象/数组
                if ((value.isObject() && ((ObjectNode) value).isEmpty()) || (value.isArray() && value.isEmpty())) {
                    iterator.remove();
                }
            }
        }
        return objectNode;
    } else if (node.isArray()) {
        ArrayNode arrayNode = (ArrayNode) node;
        Iterator<JsonNode> iterator = arrayNode.iterator();
        while (iterator.hasNext()) {
            JsonNode element = iterator.next();
            if (element.isNull()) {
                iterator.remove();
            } else {
                removeNullNodes(element);
            }
        }
        return arrayNode;
    }
    return node;
}

修改validateJson方法加入过滤步骤:

JsonNode jsonNode = object instanceof JsonNode ? (JsonNode) object : objectMapper.convertValue(object, JsonNode.class);
// 过滤NullNode
jsonNode = removeNullNodes(jsonNode);
jsonNode = schema.validate(jsonNode);

方法2:自定义序列化器利用已有配置

继承AbstractKafkaJsonSchemaSerializer重写validateJson方法,先通过序列化JSON字符串触发NON_NULL过滤,再解析回JsonNode进行校验:

public class CustomJsonSchemaSerializer<T> extends AbstractKafkaJsonSchemaSerializer<T> {
    @Override
    protected T validateJson(T object, JsonSchema schema) throws SerializationException {
        try {
            // 序列化字符串时应用NON_NULL过滤
            String jsonStr = objectMapper.writeValueAsString(object);
            // 解析为JsonNode
            JsonNode jsonNode = objectMapper.readTree(jsonStr);
            jsonNode = schema.validate(jsonNode);
            // 转换回原类型返回
            return objectMapper.convertValue(jsonNode, (Class<T>) object.getClass());
        } catch (JsonProcessingException e) {
            throw new SerializationException("Failed to process JSON", e);
        }
    }
}

这种方法复用已有配置,代码简洁,但会多一次序列化+反序列化的开销,适合对性能要求不极致的场景。

内容的提问来源于stack exchange,提问作者wojas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 16:12:13