在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:wI2L/jsondiff开源软件地址:https://github.com/wI2L/jsondiff开源编程语言:Go 98.2%开源软件介绍:jsondiffjsondiff is a Go package for computing the diff between two JSON documents as a series of RFC6902 (JSON Patch) operations, which is particularly suitable to create the patch response of a Kubernetes Mutating Webhook for example. UsageFirst, get the latest version of the library using the following command: $ go get github.com/wI2L/jsondiff@latest
Example use casesKubernetes Dynamic Admission ControllerThe typical use case within an application is to compare two values of the same type that represents the source and desired target of a JSON document. A concrete application of that would be to generate the patch returned by a Kubernetes dynamic admission controller to mutate a resource. Thereby, instead of generating the operations, just copy the source in order to apply the required changes and delegate the patch generation to the library. For example, given the following import corev1 "k8s.io/api/core/v1"
pod := corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "webserver",
Image: "nginx:latest",
VolumeMounts: []corev1.VolumeMount{{
Name: "shared-data",
MountPath: "/usr/share/nginx/html",
}},
}},
Volumes: []corev1.Volume{{
Name: "shared-data",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumMemory,
},
},
}},
},
} The first step is to copy the original pod value. The newPod := pod.DeepCopy()
// or
podBytes, err := json.Marshal(pod)
if err != nil {
// handle error
} Secondly, make some changes to the pod spec. Here we modify the image and the storage medium used by the pod's volume // Update the image of the webserver container.
newPod.Spec.Containers[0].Image = "nginx:1.19.5-alpine"
// Switch storage medium from memory to default.
newPod.Spec.Volumes[0].EmptyDir.Medium = corev1.StorageMediumDefault Finally, generate the patch that represents the changes relative to the original value. Note that when the import "github.com/wI2L/jsondiff"
patch, err := jsondiff.Compare(pod, newPod)
if err != nil {
// handle error
}
b, err := json.MarshalIndent(patch, "", " ")
if err != nil {
// handle error
}
os.Stdout.Write(b) The output is similar to the following: [{
"op": "replace",
"path": "/spec/containers/0/image",
"value": "nginx:1.19.5-alpine"
}, {
"op": "remove",
"path": "/spec/volumes/0/emptyDir/medium"
}] The JSON patch can then be used in the response payload of you Kubernetes webhook. Optional fields gotchaNote that the above example is used for simplicity, but in a real-world admission controller, you should create the diff from the raw bytes of the
A realistic usage would be similar to the following snippet: podBytes, err := json.Marshal(pod)
if err != nil {
// handle error
}
// req is a k8s.io/api/admission/v1.AdmissionRequest object
jsondiff.CompareJSON(req.AdmissionRequest.Object.Raw, podBytes) Mutating the original pod object or a copy is up to you, as long as you use the raw bytes of the You can find a detailed description of that problem and its resolution in this GitHub issue. Outdated package versionThere's also one other downside to the above example. If your webhook does not have the latest version of the For example, if your webhook mutate Diff optionsIf more control over the diff behaviour is required, use the Note that any combination of options can be used without issues. Operations factorizationBy default, when computing the difference between two JSON documents, the package does not produce For instance, given the following document: {
"a": [1, 2, 3],
"b": { "foo": "bar" }
} In order to obtain this updated version: {
"a": [1, 2, 3],
"c": [1, 2, 3],
"d": { "foo": "bar" }
} The package generates the following patch: [
{ "op": "remove", "path": "/b" },
{ "op": "add", "path": "/c", "value": [1, 2, 3] },
{ "op": "add", "path": "/d", "value": { "foo": "bar" } }
] If we take the previous example and generate the patch with factorization enabled, we then get a different patch, containing [
{ "op": "copy", "from": "/a", "path": "/c" },
{ "op": "move", "from": "/b", "path": "/d" }
] Operations rationalizationThe default method used to compare two JSON documents is a recursive comparison. This produce one or more operations for each difference found. On the other hand, in certain situations, it might be beneficial to replace a set of operations representing several changes inside a JSON node by a single replace operation targeting the parent node. For that purpose, you can use the Let's illustrate that with the following document: {
"a": { "b": { "c": { "1": 1, "2": 2, "3": 3 } } }
} In order to obtain this updated version: {
"a": { "b": { "c": { "x": 1, "y": 2, "z": 3 } } }
} The expected output is one remove/add operation combo for each children field of the object located at path [
{ "op": "remove", "path": "/a/b/c/1" },
{ "op": "remove", "path": "/a/b/c/2" },
{ "op": "remove", "path": "/a/b/c/3" },
{ "op": "add", "path": "/a/b/c/x", "value": 1 },
{ "op": "add", "path": "/a/b/c/y", "value": 2 },
{ "op": "add", "path": "/a/b/c/z", "value": 3 }
] If we also enable factorization, as seen above, we can reduce the number of operations by half: [
{ "op": "move", "from": "/a/b/c/1", "path": "/a/b/c/x" },
{ "op": "move", "from": "/a/b/c/2", "path": "/a/b/c/y" },
{ "op": "move", "from": "/a/b/c/3", "path": "/a/b/c/z" }
] And finally, with rationalization enabled, those operations are replaced with a single [
{ "op": "replace", "path": "/a/b/c", "value": { "x": 1, "y": 2, "z": 3 } }
] Invertible patchUsing the functional option However, note that it comes with one limitation. For example, let's generate the diff between those two JSON documents: {
"a": "1",
"b": "2"
} {
"a": "3",
"c": "4"
} The patch is similar to the following: [
{ "op": "test", "path": "/a", "value": "1" },
{ "op": "replace", "path": "/a", "value": "3" },
{ "op": "test", "path": "/b", "value": "2" },
{ "op": "remove", "path": "/b" },
{ "op": "add", "path": "/c", "value": "4" }
] As you can see, the Finally, as a side example, if we were to use the [
{ "op": "test", "path": "", "value": { "a": "1", "b": "2" } },
{ "op": "replace", "path": "", "value": { "a": "3", "c": "4" } }
] EquivalenceSome data types, such as arrays, can be deeply unequal and equivalent at the same time. Take the following JSON documents: [
"a", "b", "c", "d"
] [
"d", "c", "b", "a"
] The root arrays of each document are not equal because the values differ at each index. However, they are equivalent in terms of content:
For such situations, you can use the BenchmarksPerformance is not the primary target of the package, instead it strives for correctness. A simple benchmark that compare the performance of available options is provided to give a rough estimate of the cost of each option. You can find the JSON documents used by this benchmark in the directory testdata/benchs. If you'd like to run the benchmark yourself, use the following command: go get github.com/cespare/prettybench
go test -bench=. | prettybench ResultsThe benchmark was run 10x (statistics computed with benchstat) on a MacBook Pro 15", with the following specs:
Outputname time/op Compare/Compare/default-8 32.7µs ± 1% Compare/CompareJSON/default-8 24.2µs ± 0% Compare/differ_diff/default-8 5.22µs ± 0% Compare/Compare/invertible-8 33.5µs ± 0% Compare/CompareJSON/invertible-8 25.0µs ± 0% Compare/differ_diff/invertible-8 6.05µs ± 0% Compare/Compare/factorize-8 35.4µs ± 1% Compare/CompareJSON/factorize-8 26.7µs ± 0% Compare/differ_diff/factorize-8 7.55µs ± 1% Compare/Compare/rationalize-8 43.3µs ± 1% Compare/CompareJSON/rationalize-8 51.0µs ± 1% Compare/differ_diff/rationalize-8 30.6µs ± 0% Compare/Compare/factor+ratio-8 45.5µs ± 0% Compare/CompareJSON/factor+ratio-8 50.8µs ± 0% Compare/differ_diff/factor+ratio-8 29.9µs ± 0% Compare/Compare/all-options-8 53.2µs ± 1% Compare/CompareJSON/all-options-8 58.6µs ± 1% Compare/differ_diff/all-options-8 37.4µs ± 1% CreditsThis package has been inspired by existing implementations of JSON Patch in various languages:
License
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论