Install json-patch
masterTo install the latest version of the jsonpatch library, use the following command:
go get -u github.com/evanphx/json-patch/v5If you specifically require version 4, use:
go get -u gopkg.in/evanphx/json-patch.v4repository·master·Indexed 22 days ago
https://github.com/evanphx/json-patchA Go library for working with JSON patches, supporting both RFC6902 (JSON Patch) and RFC7396 (JSON Merge Patch) standards. It provides functionality for creating, applying, and combining patches, as well as comparing JSON documents for structural equality. The library includes a command-line tool for applying patch files to JSON documents via stdin.
To install the latest version of the jsonpatch library, use the following command:
go get -u github.com/evanphx/json-patch/v5If you specifically require version 4, use:
go get -u gopkg.in/evanphx/json-patch.v4The library provides two ways to configure behavior: via global variables (which affect jsonpatch.Apply) or via jsonpatch.ApplyOptions (used with jsonpatch.ApplyWithOptions).
jsonpatch.SupportNegativeIndices: (bool) Defaults to true. Enables non-standard negative indices for arrays (counting from the end). Set to false to disable.jsonpatch.AccumulatedCopySizeLimit: (int) Limits the total size increase in bytes caused by "copy" operations. Defaults to 0 (no limit).For more granular control, use jsonpatch.ApplyWithOptions with an *jsonpatch.ApplyOptions struct. You can create an instance populated with current global settings using jsonpatch.NewApplyOptions().
Additional options in jsonpatch.ApplyOptions:
AllowMissingPathOnRemove: (bool) If true, remove operations targeting a non-existent path are ignored instead of returning an error. Defaults to false.EnsurePathExistsOnAdd: (bool) If true, add operations will automatically create any missing elements in the target object's path.To check if two JSON documents are structurally identical (ignoring whitespace and key ordering), use jsonpatch.Equal(document1, document2). This is safer than comparing raw byte arrays or strings.
package main
import (
"fmt"
jsonpatch "github.com/evanphx/json-patch"
)
func main() {
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
similar := []byte(`
{
"age": 24,
"height": 3.21,
"name": "John"
}
`)
different := []byte(`{"name": "Jane", "age": 20, "height": 3.37}`)
if jsonpatch.Equal(original, similar) {
fmt.Println(`"original" is structurally equal to "similar"`)
}
if !jsonpatch.Equal(original, different) {
fmt.Println(`"original" is _not_ structurally equal to "different"`)
}
}To work with RFC6902 JSON Patches (sequences of operations like add, remove, replace), use jsonpatch.DecodePatch([]byte) to create a patch object. You can then call the .Apply(document) method on that patch object to modify a JSON document.
package main
import (
"fmt"
jsonpatch "github.com/evanphx/json-patch"
)
func main() {
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
patchJSON := []byte(`[
{"op": "replace", "path": "/name", "value": "Jane"},
{"op": "remove", "path": "/height"}
]`)
patch, err := jsonpatch.DecodePatch(patchJSON)
if err != nil {
panic(err)
}
modified, err := patch.Apply(original)
if err != nil {
panic(err)
}
fmt.Printf("Original document: %s\n", original)
fmt.Printf("Modified document: %s\n", modified)
}You can generate a Merge Patch by comparing an original document to a target document using jsonpatch.CreateMergePatch(original, target). To apply a merge patch to a document, use jsonpatch.MergePatch(document, patch).
Note: A Merge Patch describes the changes needed to convert the original to the target.
package main
import (
"fmt"
jsonpatch "github.com/evanphx/json-patch"
)
func main() {
// Let's create a merge patch from these two documents...
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
target := []byte(`{"name": "Jane", "age": 24}`)
patch, err := jsonpatch.CreateMergePatch(original, target)
if err != nil {
panic(err)
}
// Now lets apply the patch against a different JSON document...
alternative := []byte(`{"name": "Tina", "age": 28, "height": 3.75}`)
modifiedAlternative, err := jsonpatch.MergePatch(alternative, patch)
fmt.Printf("patch document: %s\n", patch)
fmt.Printf("updated alternative doc: %s\n", modifiedAlternative)
}If you have multiple RFC7396 Merge Patches, you can combine them into a single patch using jsonpatch.MergeMergePatches(patch1, patch2). Applying the combined patch is equivalent to applying the individual patches in succession.
package main
import (
"fmt"
jsonpatch "github.com/evanphx/json-patch"
)
func main() {
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
nameAndHeight := []byte(`{"height":null,"name":"Jane"}`)
ageAndEyes := []byte(`{"age":4.23,"eyes":"blue"}`)
// Let's combine these merge patch documents...
combinedPatch, err := jsonpatch.MergeMergePatches(nameAndHeight, ageAndEyes)
if err != nil {
panic(err)
}
// Apply each patch individual against the original document
withoutCombinedPatch, err := jsonpatch.MergePatch(original, nameAndHeight)
if err != nil {
panic(err)
}
withoutCombinedPatch, err = jsonpatch.MergePatch(withoutCombinedPatch, ageAndEyes)
if err != nil {
panic(err)
}
// Apply the combined patch against the original document
withCombinedPatch, err := jsonpatch.MergePatch(original, combinedPatch)
if err != nil {
panic(err)
}
// Do both result in the same thing? They should!
if jsonpatch.Equal(withCombinedPatch, withoutCombinedPatch) {
fmt.Println("Both JSON documents are structurally the same!")
}
fmt.Printf("combined merge patch: %s", combinedPatch)
}Use NewApplyOptions to get a default configuration, then modify the fields to control patch behavior:
SupportNegativeIndices (bool): If true, allows non-standard negative indices in array paths to reference elements from the end of the array. Default is true.AccumulatedCopySizeLimit (int64): Limits the total size increase in bytes caused by copy operations. If set to a value > 0, the patch will fail if this limit is exceeded.AllowMissingPathOnRemove (bool): If true, remove operations will not fail if the target path does not exist. Default is false.EnsurePathExistsOnAdd (bool): If true, add operations will recursively create missing parts of the path (creating objects or arrays as needed). Default is false.EscapeHTML (bool): Controls whether HTML characters are escaped during marshaling. Default is true.The json-patch command-line tool allows you to apply one or more JSON patch files to a JSON document provided via stdin.
Installation:
go install github.com/evanphx/json-patch/cmd/json-patchUsage:
Use the -p flag to specify patch files. The tool will apply them in the order provided.
cat document.json | json-patch -p patch.1.json -p patch.2.jsongo install github.com/evanphx/json-patch/cmd/json-patch
cat document.json | json-patch -p patch.1.json -p patch.2.jsonUse MergeMergePatches(patch1Data, patch2Data []byte) to combine two separate merge patches into a single patch. The resulting patch, when applied to a document, will yield the same result as applying patch1 and then patch2 in succession.
Returns the combined patch as a byte slice or an error if the input is invalid.
Equal(a, b []byte) to determine if two JSON documents have the same structural equality, regardless of key order or whitespace.The Patch type provides several methods to apply operations to a JSON document (byte slice):
Apply(doc []byte): Applies the patch using default ApplyOptions and returns the mutated document.ApplyWithOptions(doc []byte, options *ApplyOptions): Applies the patch using custom configuration.ApplyIndent(doc []byte, indent string): Applies the patch and returns the result formatted with the specified indentation.ApplyIndentWithOptions(doc []byte, indent string, options *ApplyOptions): Applies the patch with both custom indentation and custom configuration.An Operation is a map representing a single JSON-Patch step. You can extract specific fields using these methods:
Kind() string: Returns the operation type (e.g., add, remove, replace, move, copy, test). Returns "unknown" if the field is missing or invalid.Path() (string, error): Returns the target path for the operation. Returns ErrMissing if the field is absent.From() (string, error): Returns the source path (used by move and copy). Returns ErrMissing if the field is absent.ValueInterface() (interface{}, error): Decodes the value field into a Go interface. Returns ErrMissing if the field is absent.