jsonpatch

package module
v5.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 28, 2024 License: BSD-3-Clause Imports: 7 Imported by: 8

README

JSON-Patch

jsonpatch is a library which provides functionality for both applying RFC6902 JSON patches against documents, as well as for calculating & applying RFC7396 JSON merge patches.

GoDoc Build Status Report Card

Get It!

Latest and greatest:

go get -u github.com/evanphx/json-patch/v5

Stable Versions:

  • Version 5: go get -u gopkg.in/evanphx/json-patch.v5
  • Version 4: go get -u gopkg.in/evanphx/json-patch.v4

(previous versions below v3 are unavailable)

Use It!

Configuration

  • There is a global configuration variable jsonpatch.SupportNegativeIndices. This defaults to true and enables the non-standard practice of allowing negative indices to mean indices starting at the end of an array. This functionality can be disabled by setting jsonpatch.SupportNegativeIndices = false.

  • There is a global configuration variable jsonpatch.AccumulatedCopySizeLimit, which limits the total size increase in bytes caused by "copy" operations in a patch. It defaults to 0, which means there is no limit.

These global variables control the behavior of jsonpatch.Apply.

An alternative to jsonpatch.Apply is jsonpatch.ApplyWithOptions whose behavior is controlled by an options parameter of type *jsonpatch.ApplyOptions.

Structure jsonpatch.ApplyOptions includes the configuration options above and adds two new options: AllowMissingPathOnRemove and EnsurePathExistsOnAdd.

When AllowMissingPathOnRemove is set to true, jsonpatch.ApplyWithOptions will ignore remove operations whose path points to a non-existent location in the JSON document. AllowMissingPathOnRemove defaults to false which will lead to jsonpatch.ApplyWithOptions returning an error when hitting a missing path on remove.

When EnsurePathExistsOnAdd is set to true, jsonpatch.ApplyWithOptions will make sure that add operations produce all the path elements that are missing from the target object.

Use jsonpatch.NewApplyOptions to create an instance of jsonpatch.ApplyOptions whose values are populated from the global configuration variables.

Create and apply a merge patch

Given both an original JSON document and a modified JSON document, you can create a Merge Patch document.

It can describe the changes needed to convert from the original to the modified JSON document.

Once you have a merge patch, you can apply it to other JSON documents using the jsonpatch.MergePatch(document, patch) function.

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)
}

When ran, you get the following output:

$ go run main.go
patch document:   {"height":null,"name":"Jane"}
updated alternative doc: {"age":28,"name":"Jane"}

Create and apply a JSON Patch

You can create patch objects using DecodePatch([]byte), which can then be applied against JSON documents.

The following is an example of creating a patch from two operations, and applying it against 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)
}

When ran, you get the following output:

$ go run main.go
Original document: {"name": "John", "age": 24, "height": 3.21}
Modified document: {"age":24,"name":"Jane"}

Comparing JSON documents

Due to potential whitespace and ordering differences, one cannot simply compare JSON strings or byte-arrays directly.

As such, you can instead use jsonpatch.Equal(document1, document2) to determine if two JSON documents are structurally equal. This ignores whitespace differences, and key-value ordering.

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"`)
	}
}

When ran, you get the following output:

$ go run main.go
"original" is structurally equal to "similar"
"original" is _not_ structurally equal to "different"

Combine merge patches

Given two JSON merge patch documents, it is possible to combine them into a single merge patch which can describe both set of changes.

The resulting merge patch can be used such that applying it results in a document structurally similar as merging each merge patch to the document 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)
}

When ran, you get the following output:

$ go run main.go
Both JSON documents are structurally the same!
combined merge patch: {"age":4.23,"eyes":"blue","height":null,"name":"Jane"}

CLI for comparing JSON documents

You can install the commandline program json-patch.

This program can take multiple JSON patch documents as arguments, and fed a JSON document from stdin. It will apply the patch(es) against the document and output the modified doc.

patch.1.json

[
    {"op": "replace", "path": "/name", "value": "Jane"},
    {"op": "remove", "path": "/height"}
]

patch.2.json

[
    {"op": "add", "path": "/address", "value": "123 Main St"},
    {"op": "replace", "path": "/age", "value": "21"}
]

document.json

{
    "name": "John",
    "age": 24,
    "height": 3.21
}

You can then run:

$ go install github.com/evanphx/json-patch/cmd/json-patch
$ cat document.json | json-patch -p patch.1.json -p patch.2.json
{"address":"123 Main St","age":"21","name":"Jane"}

Help It!

Contributions are welcomed! Leave an issue or create a PR.

Before creating a pull request, we'd ask that you make sure tests are passing and that you have added new tests when applicable.

Contributors can run tests using:

go test -cover ./...

Builds for pull requests are tested automatically using GitHub Actions.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// SupportNegativeIndices decides whether to support non-standard practice of
	// allowing negative indices to mean indices starting at the end of an array.
	// Default to true.
	SupportNegativeIndices bool = true
	// AccumulatedCopySizeLimit limits the total size increase in bytes caused by
	// "copy" operations in a patch.
	AccumulatedCopySizeLimit int64 = 0
)
View Source
var (
	ErrTestFailed   = errors.New("test failed")
	ErrMissing      = errors.New("missing value")
	ErrUnknownType  = errors.New("unknown object type")
	ErrInvalid      = errors.New("invalid state detected")
	ErrInvalidIndex = errors.New("invalid index referenced")
)
View Source
var ErrBadJSONDoc = fmt.Errorf("Invalid JSON Document")
View Source
var ErrBadJSONPatch = fmt.Errorf("Invalid JSON Patch")

Functions

func CreateMergePatch

func CreateMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error)

CreateMergePatch will return a merge patch document capable of converting the original document(s) to the modified document(s). The parameters can be bytes of either two JSON Documents, or two arrays of JSON documents. The merge patch returned follows the specification defined at http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-07

func Equal

func Equal(a, b []byte) bool

Equal indicates if 2 JSON documents have the same structural equality.

func MergeMergePatches

func MergeMergePatches(patch1Data, patch2Data []byte) ([]byte, error)

MergeMergePatches merges two merge patches together, such that applying this resulting merged merge patch to a document yields the same as merging each merge patch to the document in succession.

func MergePatch

func MergePatch(docData, patchData []byte) ([]byte, error)

MergePatch merges the patchData into the docData.

Types

type AccumulatedCopySizeError

type AccumulatedCopySizeError struct {
	// contains filtered or unexported fields
}

AccumulatedCopySizeError is an error type returned when the accumulated size increase caused by copy operations in a patch operation has exceeded the limit.

func NewAccumulatedCopySizeError

func NewAccumulatedCopySizeError(l, a int64) *AccumulatedCopySizeError

NewAccumulatedCopySizeError returns an AccumulatedCopySizeError.

func (*AccumulatedCopySizeError) Error

func (a *AccumulatedCopySizeError) Error() string

Error implements the error interface.

type ArraySizeError

type ArraySizeError struct {
	// contains filtered or unexported fields
}

ArraySizeError is an error type returned when the array size has exceeded the limit.

func NewArraySizeError

func NewArraySizeError(l, s int) *ArraySizeError

NewArraySizeError returns an ArraySizeError.

func (*ArraySizeError) Error

func (a *ArraySizeError) Error() string

Error implements the error interface.

type Operation

type Operation map[string]*json.RawMessage

Operation is a single JSON-Patch step, such as a single 'add' operation.

func (Operation) From

func (o Operation) From() (string, error)

From reads the "from" field of the Operation.

func (Operation) Kind

func (o Operation) Kind() string

Kind reads the "op" field of the Operation.

func (Operation) Path

func (o Operation) Path() (string, error)

Path reads the "path" field of the Operation.

func (Operation) ValueInterface

func (o Operation) ValueInterface() (interface{}, error)

ValueInterface decodes the operation value into an interface.

type Patch

type Patch []Operation

Patch is an ordered collection of Operations.

func DecodePatch

func DecodePatch(buf []byte) (Patch, error)

DecodePatch decodes the passed JSON document as an RFC 6902 patch.

func (Patch) Apply

func (p Patch) Apply(doc []byte) ([]byte, error)

Apply mutates a JSON document according to the patch, and returns the new document.

func (Patch) ApplyIndent

func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error)

ApplyIndent mutates a JSON document according to the patch, and returns the new document indented.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL