登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go JSONPatch 怎样合并两份 JSON Merge Patch

来源:17golang原创

时间:2026-10-29 12:36:27 208浏览 收藏

两份 JSON Merge Patch 不必先手动解码成 map[string]any 再拼接。Go 中可以直接调用 jsonpatch.MergeMergePatches(patchA, patchB) 得到一份组合补丁,再用 jsonpatch.MergePatch(document, combinedPatch) 应用到原文档。需要特别留意补丁顺序:同名字段由后一个补丁覆盖,null 表示删除对象字段,数组则按整体值替换。

官方地址:https://github.com/evanphx/json-patch

如果两份输入都是 JSON Merge Patch,优先使用 MergeMergePatches;它表达的是“先应用第一份,再应用第二份”的合并结果,不是 JSON Patch 数组操作的拼接。

项目目标:把两份局部配置合成一份

假设一个配置中心收到两份局部更新:团队补丁负责改姓名和身高,功能补丁负责改年龄、增加眼睛颜色。我们希望把它们组合成一份补丁,交给不同环境的原始文档重复使用。

依赖使用 v5 模块路径:

# 初始化一个最小 Go 项目,并引入 JSON Patch 库
go mod init example.com/mergepatch-demo
go get github.com/evanphx/json-patch/v5

先把输入拆成两个对象形状的 Merge Patch。对象字段只代表需要变更的部分,因此不需要把完整配置复制两遍。

package main

import "fmt"

func main() {
	// 团队补丁只负责身份和身高字段。
	teamPatch := []byte(`{"name":"Jane","height":null}`)
	// 功能补丁只负责年龄和新字段,后者不会影响前一个补丁的 name。
	featurePatch := []byte(`{"age":28,"eyes":"blue"}`)

	fmt.Printf("team=%s feature=%s\n", teamPatch, featurePatch)
}
原文档、两份局部 JSON Merge Patch、MergeMergePatches 和组合补丁的关系说明图
图1:静态结构说明图,查看两份 JSON Merge Patch 如何进入组合函数并得到组合补丁。

核心代码:调用 MergeMergePatches

MergeMergePatches 接收两段 JSON 字节,并返回新的 JSON 字节。它不是把两个字符串直接拼接,而是按 Merge Patch 的对象语义递归处理字段;输入非法时要保留错误,不要把结果当成可用配置继续向下传。

package main

import (
	"fmt"

	jsonpatch "github.com/evanphx/json-patch/v5"
)

func composeAndApply(document, first, second []byte) ([]byte, error) {
	// second 放在后面,冲突字段会采用第二份补丁的值。
	combined, err := jsonpatch.MergeMergePatches(first, second)
	if err != nil {
		return nil, fmt.Errorf("合并 Merge Patch 失败: %w", err)
	}

	// 组合补丁可以复用于另一份结构相同的原始文档。
	result, err := jsonpatch.MergePatch(document, combined)
	if err != nil {
		return nil, fmt.Errorf("应用组合补丁失败: %w", err)
	}
	return result, nil
}

func main() {
	// 原文档保留未被补丁提到的字段,供局部更新使用。
	document := []byte(`{"name":"John","age":24,"height":3.21}`)
	first := []byte(`{"name":"Jane","height":null}`)
	second := []byte(`{"age":28,"eyes":"blue"}`)

	result, err := composeAndApply(document, first, second)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(result))
}

这个小项目的关键是把“组合”与“应用”分开:前者只产生新的补丁,后者才需要原始文档。这样同一份组合补丁可以用于测试配置、灰度配置和正式配置,而不必修改输入字节。

如何确认组合语义与依次应用一致

工程代码中可以把两种路径并排比较:路径一先应用第一份,再应用第二份;路径二只应用组合补丁。库提供的 Equal 比较 JSON 结构而不是字节顺序,适合处理对象键顺序不同但内容相同的情况。

// sequentialApply 表示按接收顺序逐份应用补丁。
func sequentialApply(document, first, second []byte) ([]byte, error) {
	step, err := jsonpatch.MergePatch(document, first)
	if err != nil {
		return nil, err
	}
	return jsonpatch.MergePatch(step, second)
}

// comparePaths 用结构比较避免把 JSON 字符串顺序误判为结果不同。
func comparePaths(document, first, second []byte) error {
	combined, err := jsonpatch.MergeMergePatches(first, second)
	if err != nil {
		return err
	}
	left, err := sequentialApply(document, first, second)
	if err != nil {
		return err
	}
	right, err := jsonpatch.MergePatch(document, combined)
	if err != nil {
		return err
	}
	if !jsonpatch.Equal(left, right) {
		return fmt.Errorf("两条应用路径的 JSON 结构不一致")
	}
	return nil
}

对上面的输入,组合补丁的结构可以理解为:

{
  "age": 28,
  "eyes": "blue",
  "height": null,
  "name": "Jane"
}

这里的 JSON 只是预期的结构示意。真正的判断应交给代码中的结构比较,并在业务边界上保留错误处理。

JSON Merge Patch 中普通字段、null 删除、嵌套对象、数组值与最终文档的语义边界说明图
图2:语义结构说明图,查看普通字段更新、null 删除、嵌套对象递归和数组整体替换。

四个容易误用的边界

输入情况合并时的理解工程提醒
同名普通字段后一个补丁的值覆盖前一个补丁定义清楚补丁顺序,必要时记录来源
字段值为 null对象字段表示删除不能把 null 当作“保留空值”的通用表达
同名嵌套对象对象成员继续按 Merge Patch 语义合并删除嵌套成员要在对应层级写 null
数组字段数组通常作为整体值替换不能用它表达数组中某个元素的精细增删

因此,Merge Patch 适合对象型配置的局部更新;如果需求是移动数组元素、按索引删除或表达一串明确操作,应评估 JSON Patch 的操作数组,而不是继续堆叠 Merge Patch。

常见问题

两份补丁有同一个字段时谁生效?

以传入顺序为准,第二份补丁覆盖第一份补丁的同名结果。调用方应固定排序规则,不要依赖 map 的遍历顺序。

为什么合并数组后不是只增加一个元素?

JSON Merge Patch 把数组看作字段值,通常会整体替换。要操作数组内部位置,应改用 JSON Patch 或在业务层定义专门的追加、删除命令。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>