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.
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}]
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.
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"}]
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.
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}}]
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
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"}]