evanphx/json-patch

repository·master·Indexed 22 days ago

https://github.com/evanphx/json-patch

A 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.

Tokens
3.7K
Snippets
7
Records
20
Agent score
78%

What's inside evanphx/json-patch

  1. Configure jsonpatch behavior

    master

    The library provides two ways to configure behavior: via global variables (which affect jsonpatch.Apply) or via jsonpatch.ApplyOptions (used with jsonpatch.ApplyWithOptions).

    Global Configuration

    • 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).

    ApplyWithOptions and ApplyOptions

    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.
  2. Compare JSON documents for structural equality

    master

    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"`)
    	}
    }
  3. Create and apply an RFC6902 JSON Patch

    master

    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)
    }
  4. Create and apply an RFC7396 JSON Merge Patch

    master

    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)
    }
  5. Combine multiple JSON Merge Patches

    master

    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)
    }
  6. Configure ApplyOptions for patch application

    master

    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.
  7. Use the json-patch CLI to apply patches

    master

    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-patch

    Usage: 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.json
    go install github.com/evanphx/json-patch/cmd/json-patch
    cat document.json | json-patch -p patch.1.json -p patch.2.json
  8. Merge two JSON Merge Patches with MergeMergePatches

    master

    Use 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.

  9. Apply a JSON Patch to a document

    master

    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.
  10. Inspect Operation details

    master

    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.