Skip to content

jsonpatch is a Go library to create JSON patches (RFC6902) directly from arbitrary Go objects and facilitates the implementation of sophisticated custom (e.g. filtered, validated) patch creation.

License

Notifications You must be signed in to change notification settings

snorwin/jsonpatch

Repository files navigation

jsonpatch

GitHub Action Documentation Test Go Report Card Coverage Status Releases License: MIT

jsonpatch is a Go library to create JSON patches (RFC6902) directly from arbitrary Go objects and facilitates the implementation of sophisticated custom (e.g. filtered, validated) patch creation.

Basic Example

package main

import (
	"fmt"

	"github.com/snorwin/jsonpatch"
)

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	original := &Person{
		Name: "John Doe",
		Age:  42,
	}
	updated := &Person{
		Name: "Jane Doe",
		Age:  21,
	}

	patch, _ := jsonpatch.CreateJSONPatch(updated, original)
	fmt.Println(patch.String())
}
[{"op":"replace","path":"/name","value":"Jane Doe"},{"op":"replace","path":"/age","value":21}]

Options

Filter patches using Predicates

The option WithPredicate sets a patch Predicate which can be used to filter or validate the patch creation. For each kind of patch (add, remove and replace) a dedicated filter function can be configured. The predicate will be checked before a patch is created, or the JSON object is processed further.

Example

package main

import (
	"fmt"

	"github.com/snorwin/jsonpatch"
)

type Job struct {
	Position  string `json:"position"`
	Company   string `json:"company"`
	Volunteer bool   `json:"volunteer"`
}

func main() {
	original := []Job{
		{Position: "IT Trainer", Company: "Powercoders", Volunteer: true},
		{Position: "Software Engineer", Company: "Github"},
	}
	updated := []Job{
		{Position: "Senior IT Trainer", Company: "Powercoders", Volunteer: true},
		{Position: "Senior Software Engineer", Company: "Github"},
	}

	patch, _ := jsonpatch.CreateJSONPatch(updated, original, jsonpatch.WithPredicate(jsonpatch.Funcs{
		ReplaceFunc: func(pointer jsonpatch.JSONPointer, value, _ interface{}) bool {
			// only update volunteering jobs
			if job, ok := value.(Job); ok {
				return job.Volunteer
			}
			return true
		},
	}))
	fmt.Println(patch.String())
}
[{"op":"replace","path":"/0/position","value":"Senior IT Trainer"}]

Create partial patches

The option WithPrefix is used to specify a JSON pointer prefix if only a sub part of JSON structure needs to be patched, but the patch still need to be applied on the entire JSON object.

Example

package main

import (
	"fmt"

	"github.com/snorwin/jsonpatch"
)

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Jobs []Job  `json:"jobs"`
}

type Job struct {
	Position  string `json:"position"`
	Company   string `json:"company"`
	Volunteer bool   `json:"volunteer"`
}

func main() {
	original := &Person{
		Name: "John Doe",
		Age:  42,
		Jobs: []Job{{Position: "IT Trainer", Company: "Powercoders"}},
	}
	updated := []Job{
		{Position: "Senior IT Trainer", Company: "Powercoders", Volunteer: true},
		{Position: "Software Engineer", Company: "Github"},
	}
	
	patch, _ := jsonpatch.CreateJSONPatch(updated, original.Jobs, jsonpatch.WithPrefix(jsonpatch.ParseJSONPointer("/jobs")))
	fmt.Println(patch.String())
}
[{"op":"replace","path":"/jobs/0/position","value":"Senior IT Trainer"},{"op":"replace","path":"/jobs/0/volunteer","value":true},{"op":"add","path":"/jobs/1","value":{"position":"Software Engineer","company":"Github","volunteer":false}}]

Ignore slice order

There are two options to ignore the slice order:

  • IgnoreSliceOrder will ignore the order of all slices of built-in types (e.g. int, string) during the patch creation and will instead use the value itself in order to match and compare the current and modified JSON.
  • IgnoreSliceOrderWithPattern allows to specify for which slices the order should be ignored using JSONPointer patterns (e.g. /jobs, /jobs/*). Furthermore, the slice order of structs (and pointer of structs) slices can be ignored by specifying a JSON field which should be used to match the struct values.

NOTE: Ignoring the slice order only works if the elements (or the values used to match structs) are unique

Example

package main

import (
	"fmt"

	"github.com/snorwin/jsonpatch"
)

type Person struct {
	Name       string   `json:"name"`
	Pseudonyms []string `json:"pseudonyms"`
	Jobs       []Job    `json:"jobs"`
}

type Job struct {
	Position  string `json:"position"`
	Company   string `json:"company"`
	Volunteer bool   `json:"volunteer"`
}

func main() {
	original := Person{
		Name:       "John Doe",
		Pseudonyms: []string{"Jo", "JayD"},
		Jobs: []Job{
			{Position: "Software Engineer", Company: "Github"},
			{Position: "IT Trainer", Company: "Powercoders"},
		},
	}
	updated := Person{
		Name:       "John Doe",
		Pseudonyms: []string{"Jonny", "Jo"},
		Jobs: []Job{
			{Position: "IT Trainer", Company: "Powercoders", Volunteer: true},
			{Position: "Senior Software Engineer", Company: "Github"},
		},
	}

	patch, _ := jsonpatch.CreateJSONPatch(updated, original,
		jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{Pattern: "/*", JSONField: "company"}}),
		jsonpatch.IgnoreSliceOrder(),
	)
	fmt.Println(patch.String())
}
[{"op":"add","path":"/pseudonyms/2","value":"Jonny"},{"op":"remove","path":"/pseudonyms/1"},{"op":"replace","path":"/jobs/1/volunteer","value":true},{"op":"replace","path":"/jobs/0/position","value":"Senior Software Engineer"}]

About

jsonpatch is a Go library to create JSON patches (RFC6902) directly from arbitrary Go objects and facilitates the implementation of sophisticated custom (e.g. filtered, validated) patch creation.

Topics

Resources

License

Stars

Watchers

Forks

Languages