如何使用Jackson为两个对象创建JSON Merge Patch文档
问题背景
我们的应用重度依赖Jackson处理JSON,REST API里有大量带注解的模型对象。现在需要调用一个要求提交Merge Patch(符合RFC7396规范)JSON文档的REST API。目前用JSON-P能轻松实现,但没找到Jackson原生的实现方式。
现有实现代码如下:
public <T> String createMergePatch(T source, T target) throws IOException { var sourceBytes = objectMapper.writeValueAsBytes(source); var targetBytes = objectMapper.writeValueAsBytes(target); // 切换框架——非常不理想 var sourceJson = Json.createReader(new ByteArrayInputStream(sourceBytes)).readValue(); var targetJson = Json.createReader(new ByteArrayInputStream(targetBytes)).readValue(); var mergePatch = Json.createMergeDiff(sourceJson, targetJson); return mergePatch.toJsonValue().toString(); }
这段代码先用Jackson注解把源对象和目标对象转成JSON,然后切换到其他框架调用createMergeDiff方法。虽然输出结果正确,但这种方式效率低,还引入了不必要的依赖。
核心问题
有没有更优的Jackson原生实现方式?不需要引入其他JSON框架,也不用在JSON和对象之间做往返转换?
解决方案
可以直接利用Jackson的树模型(JsonNode)和官方扩展库,或者手动实现对比逻辑,完全基于Jackson原生能力解决问题,避免额外依赖和不必要的转换开销。
方法一:使用Jackson官方扩展库(推荐)
Jackson官方维护的jackson-datatype-json-patch扩展库原生支持RFC7396的Merge Patch规范,完全兼容现有带注解的模型对象。
步骤1:引入依赖(Maven示例)
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-json-patch</artifactId> <version>匹配你的Jackson核心版本</version> </dependency>
步骤2:实现Merge Patch生成
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsonpatch.MergePatch; import java.io.IOException; public <T> String createMergePatch(T source, T target) throws IOException { ObjectMapper mapper = new ObjectMapper(); // 直接将对象转为JsonNode,跳过字节流序列化/解析的往返步骤 JsonNode sourceNode = mapper.valueToTree(source); JsonNode targetNode = mapper.valueToTree(target); // 生成符合规范的Merge Patch MergePatch mergePatch = MergePatch.fromDiff(sourceNode, targetNode); // 将Patch转为JSON字符串 return mapper.writeValueAsString(mergePatch); }
方法二:手动实现Merge Diff逻辑(无额外依赖)
如果不想引入扩展库,可以基于Jackson的JsonNode手动实现RFC7396的对比规则:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; public <T> String createMergePatch(T source, T target) throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode sourceNode = mapper.valueToTree(source); JsonNode targetNode = mapper.valueToTree(target); ObjectNode patchNode = mapper.createObjectNode(); generateMergePatch(sourceNode, targetNode, patchNode); return mapper.writeValueAsString(patchNode); } private void generateMergePatch(JsonNode source, JsonNode target, ObjectNode patch) { if (source.equals(target)) { return; } // RFC7396规定数组直接整体替换 if (source.isArray() && target.isArray()) { patch.set("", target); return; } // 处理对象类型的字段差异 if (source.isObject() && target.isObject()) { // 新增/修改的字段 target.fieldNames().forEachRemaining(fieldName -> { JsonNode sourceField = source.get(fieldName); JsonNode targetField = target.get(fieldName); if (sourceField == null) { patch.set(fieldName, targetField); } else { generateMergePatch(sourceField, targetField, patch.withObject(fieldName)); // 递归后无差异则移除空节点 if (patch.get(fieldName).isEmpty()) { patch.remove(fieldName); } } }); // 处理字段删除:源存在但目标不存在的字段,Patch中设为null source.fieldNames().forEachRemaining(fieldName -> { if (target.get(fieldName) == null) { patch.putNull(fieldName); } }); } else { // 非对象/数组类型,直接替换 patch.set("", target); } }
方案优势
- 完全基于Jackson原生能力,无额外JSON框架依赖
- 直接将对象转为
JsonNode,避免序列化字节流再解析的往返开销,效率更高 - 完全兼容原有模型对象的Jackson注解,不破坏现有业务逻辑
内容的提问来源于stack exchange,提问作者ewramner
相关产品推荐
相关产品推荐

