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。
我有两个问题:
- 为什么明明配置了NON_NULL,还是会出现NullNode?
- 有没有优雅的方法确保最终的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
相关产品推荐
相关产品推荐

