引言
你肯定晓得 JSON 吧,那专门用于批改 JSON 内容的 JSON PATCH 规范你是否晓得呢?
RFC 6902 就定义了这么一种 JSON PATCH 规范,本文将对其进行介绍。
JSON PATCH
JSON Patch 自身也是一种 JSON 文档构造,用于示意要利用于 JSON 文档的操作序列;它实用于 HTTP PATCH
办法,其 MIME 媒体类型为 "application/json-patch+json"
。
这句话兴许不太好了解,咱们先看一个例子:
PATCH /my/data HTTP/1.1
Host: example.org
Content-Length: 326
Content-Type: application/json-patch+json
If-Match: "abc123"
[{ "op": "test", "path": "/a/b/c", "value": "foo"},
{"op": "remove", "path": "/a/b/c"},
{"op": "add", "path": "/a/b/c", "value": [ "foo", "bar"] },
{"op": "replace", "path": "/a/b/c", "value": 42},
{"op": "move", "from": "/a/b/c", "path": "/a/b/d"},
{"op": "copy", "from": "/a/b/d", "path": "/a/b/e"}
]
这个 HTTP 申请的 body 也是 JSON 格局(JSON PATCH 自身也是一种 JSON 构造),然而这个 JSON 格局是有具体标准的(只能依照规范去定义要利用于 JSON 文档的操作序列)。
具体而言,JSON Patch 的数据结构就是一个 JSON 对象数组,其中每个对象必须申明 op
去定义将要执行的操作,依据 op
操作的不同,须要对应另外申明 path
、value
或 from
字段。
再例如,
原始 JSON:
{
"a": "aaa",
"b": "bbb"
}
利用如下 JSON PATCH:
[{ "op": "replace", "path": "/a", "value": "111"},
{"op": "remove", "path": "/b"}
]
失去的后果为:
{"a": "111"}
须要留神的是:
- patch 对象中的属性没有程序要求,比方
{"op": "remove", "path": "/b"}
与{"path": "/b", "op": "remove"}
是齐全等价的。 - patch 对象的执行是依照数组程序执行的,比方上例中先执行了 replace,而后再执行 remove。
- patch 操作是原子的,即便咱们申明了多个操作,但最终的后果是要么全副胜利,要么放弃原数据不变,不存在部分变更。也就是说如果多个操作中的某个操作异样失败了,那么原数据就不变。
op
op
只能是以下操作之一:
add
remove
replace
move
copy
test
这些操作我置信不必做任何阐明你就能了解其具体的含意,惟一要阐明的可能就是 test
,test
操作其实就是查看 path
地位的值与 value
的值“相等”。
add
add
操作会依据 path
定义去执行不同的操作:
- 如果
path
是一个数组 index,那么新的value
值会被插入到执行地位。 - 如果
path
是一个不存在的对象成员,那么新的对象成员会被增加到该对象中。 - 如果
path
是一个曾经存在的对象成员,那么该对象成员的值会被value
所替换。
add
操作必须另外申明 path
和 value
。
path
指标地位必须是以下之一:
- 指标文档的根 – 如果
path
指向的是根,那么value
值就将是整个文档的内容。 - 一个已存在对象的成员 – 利用后
value
将会被增加到指定地位,如果成员已存在则其值会被替换。 - 一个已存在数组的元素 – 利用后
value
值会被增加到数组中的指定地位,任何在指定索引地位或之上的元素都会向右挪动一个地位。指定的索引不能大于数组中元素的数量。能够应用-
字符来索引数组的开端。
因为此操作旨在增加到现有对象和数组中,因而其指标地位通常不存在。只管指针的错误处理算法将被调用,但本标准定义了 add 指针的错误处理行为,以疏忽该谬误并依照指定形式增加值。
然而,对象自身或蕴含它的数组的确须要存在,并且如果不是这种状况,则依然会出错。
例如,对数据 {"a": { "foo": 1} }
执行 add 操作,path 为 “/a/b” 时不是谬误。但如果对数据 {"q": { "bar": 2} }
执行同样的操作则是一种谬误,因为 “a” 不存在。
示例:
-
add
一个对象成员# 源数据:{"foo": "bar"} # JSON Patch: [{ "op": "add", "path": "/baz", "value": "qux"} ] # 后果:{ "baz": "qux", "foo": "bar" }
-
add
一个数组元素# 源数据:{"foo": [ "bar", "baz"] } # JSON Patch: [{ "op": "add", "path": "/foo/1", "value": "qux"} ] # 后果:{"foo": [ "bar", "qux", "baz"] }
-
add
一个嵌套成员对象# 源数据:{"foo": "bar"} # JSON Patch: [{ "op": "add", "path": "/child", "value": { "grandchild": {} } } ] # 后果:{ "foo": "bar", "child": {"grandchild": {} } }
-
疏忽未辨认的元素
# 源数据:{"foo": "bar"} # JSON Patch: [{ "op": "add", "path": "/baz", "value": "qux", "xyz": 123} ] # 后果:{ "foo": "bar", "baz": "qux" }
-
add
到一个不存在的指标失败# 源数据:{"foo": "bar"} # JSON Patch: [{ "op": "add", "path": "/baz/bat", "value": "qux"} ] # 失败,因为操作的指标地位既不援用文档根,也不援用现有对象的成员,也不援用现有数组的成员。
-
add
一个数组# 源数据:{"foo": ["bar"] } # JSON Patch: [{ "op": "add", "path": "/foo/-", "value": ["abc", "def"] } ] # 后果:{"foo": ["bar", ["abc", "def"]] }
remove
remove
将会删除 path 指标地位上的值,如果 path 指向的是一个数组 index,那么右侧其余值都将左移。
示例:
-
remove
一个对象成员# 源数据:{ "baz": "qux", "foo": "bar" } # JSON Patch: [{ "op": "remove", "path": "/baz"} ] # 后果:{"foo": "bar"}
-
remove
一个数组元素# 源数据:{"foo": [ "bar", "qux", "baz"] } # JSON Patch: [{ "op": "remove", "path": "/foo/1"} ] # 后果:{"foo": [ "bar", "baz"] }
replace
replace
操作会将 path
指标地位上的值替换为 value
。此操作与 remove
后 add
同样的 path
在性能上是雷同的。
示例:
-
replace
某个值# 源数据:{ "baz": "qux", "foo": "bar" } # JSON Patch: [{ "op": "replace", "path": "/baz", "value": "boo"} ] # 后果:{ "baz": "boo", "foo": "bar" }
move
move
操作将 from
地位的值挪动到 path
地位。from
地位不能是 path
地位的前缀,也就是说,一个地位不能被挪动到它的子级中。
示例:
-
move
某个值# 源数据:{ "foo": { "bar": "baz", "waldo": "fred" }, "qux": {"corge": "grault"} } # JSON Patch: [{ "op": "move", "from": "/foo/waldo", "path": "/qux/thud"} ] # 后果:{ "foo": {"bar": "baz"}, "qux": { "corge": "grault", "thud": "fred" } }
-
move
一个数组元素# 源数据:{"foo": [ "all", "grass", "cows", "eat"] } # JSON Patch: [{ "op": "move", "from": "/foo/1", "path": "/foo/3"} ] # 后果:{"foo": [ "all", "cows", "eat", "grass"] }
copy
copy
操作将 from
地位的值复制到 path
地位。
test
test
操作会查看 path
地位的值是否与 value
“相等”。
这里,“相等”意味着 path
地位的值和 value
的值是雷同的 JSON 类型,并且它们遵循以下规定:
- 字符串:如果它们蕴含雷同数量的 Unicode 字符并且它们的码点是逐字节相等,则被视为相等。
- 数字:如果它们的值在数值上是相等的,则被视为相等。
- 数组:如果它们蕴含雷同数量的值,并且每个值能够应用此类型特定规定将其视为与另一个数组中对应地位处的值相等,则被视为相等。
- 对象:如果它们蕴含雷同数量的成员,并且每个成员能够通过比拟其键(作为字符串)和其值(应用此类型特定规定)来认为与其余对象中的成员相等,则被视为相等。
- 文本(false,true 和 null):如果它们齐全一样,则被视为相等。
请留神,所进行的比拟是逻辑比拟;例如,数组成员之间的空格不重要。
示例:
-
test
某个值胜利# 源数据:{ "baz": "qux", "foo": ["a", 2, "c"] } # JSON Patch: [{ "op": "test", "path": "/baz", "value": "qux"}, {"op": "test", "path": "/foo/1", "value": 2} ]
-
test
某个值谬误# 源数据:{"baz": "qux"} # JSON Patch: [{ "op": "test", "path": "/baz", "value": "bar"} ]
-
~
符号本义~
字符是 JSON 指针中的关键字。因而,咱们须要将其编码为〜0
# 源数据:{ "/": 9, "~1": 10 } # JSON Patch: [{"op": "test", "path": "/~01", "value": 10} ] # 后果:{ "/": 9, "~1": 10 }
-
比拟字符串和数字
# 源数据:{ "/": 9, "~1": 10 } # JSON Patch: [{"op": "test", "path": "/~01", "value": "10"} ] # 失败,因为不遵循上述相等的规定。
结语
应用 JSON PATCH 的起因之一其实是为了防止在只须要批改某一部分内容的时候从新发送整个文档。JSON PATCH 也早已利用在了 Kubernetes 等许多我的项目中。