diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2d556c8 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/JeroenSoeters/jsonpatch + +go 1.23.4 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..713a0b4 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jsonpatch.go b/jsonpatch.go index 7862b39..bfa867d 100644 --- a/jsonpatch.go +++ b/jsonpatch.go @@ -5,15 +5,84 @@ import ( "encoding/json" "fmt" "reflect" + "slices" + "strconv" "strings" ) var errBadJSONDoc = fmt.Errorf("Invalid JSON Document") +type Path string +type Key string +type EntitySets map[Path]Key + +type Collections struct { + EntitySets EntitySets + Arrays []string +} + +func (c *Collections) isArray(path string) bool { + jsonPath := toJsonPath(path) + return slices.Contains(c.Arrays, jsonPath) +} + +func (c *Collections) isEntitySet(path string) bool { + jsonPath := toJsonPath(path) + _, ok := c.EntitySets[Path(jsonPath)] + return ok +} + +func (s EntitySets) Add(path Path, key Key) { + if s == nil { + s = make(EntitySets) + } + s[path] = key +} + +func (s EntitySets) Get(path Path) (Key, bool) { + if s == nil { + return "", false + } + key, ok := s[path] + return key, ok +} + +func toJsonPath(path string) string { + if path == "" || path == "/" { + return "$" + } + + parts := strings.Split(path, "/") + var jsonPathParts []string + + for _, part := range parts { + if part == "" { + continue + } + + _, err := strconv.Atoi(part) + if err == nil { + jsonPathParts = append(jsonPathParts, "[*]") + } else { + jsonPathParts = append(jsonPathParts, "."+part) + } + } + + return "$" + strings.Join(jsonPathParts, "") +} + +type PatchStrategy string + +const ( + PatchStrategyExactMatch PatchStrategy = "exact-match" + PatchStrategyEnsureExists PatchStrategy = "ensure-exists" + PatchStrategyEnsureAbsent PatchStrategy = "ensure-absent" +) + type JsonPatchOperation struct { - Operation string `json:"op"` - Path string `json:"path"` - Value interface{} `json:"value,omitempty"` + Operation string `json:"op"` + Path string `json:"path"` + Value any `json:"value,omitempty"` } func (j *JsonPatchOperation) Json() string { @@ -45,7 +114,7 @@ func (a ByPath) Len() int { return len(a) } func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path } -func NewPatch(operation, path string, value interface{}) JsonPatchOperation { +func NewPatch(operation, path string, value any) JsonPatchOperation { return JsonPatchOperation{Operation: operation, Path: path, Value: value} } @@ -53,11 +122,12 @@ func NewPatch(operation, path string, value interface{}) JsonPatchOperation { // // 'a' is original, 'b' is the modified document. Both are to be given as json encoded content. // The function will return an array of JsonPatchOperations +// If ignoreArrayOrder is true, arrays with the same elements but in different order will be considered equal // -// An error will be returned if any of the two documents are invalid. -func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) { - var aI interface{} - var bI interface{} +// An e rror will be returned if any of the two documents are invalid. +func CreatePatch(a, b []byte, collections Collections, strategy PatchStrategy) ([]JsonPatchOperation, error) { + var aI any + var bI any err := json.Unmarshal(a, &aI) if err != nil { @@ -68,13 +138,14 @@ func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) { return nil, errBadJSONDoc } - return handleValues(aI, bI, "", []JsonPatchOperation{}) + return handleValues(aI, bI, "", []JsonPatchOperation{}, strategy, collections) } // Returns true if the values matches (must be json types) // The types of the values must match, otherwise it will always return false -// If two map[string]interface{} are given, all elements must match. -func matchesValue(av, bv interface{}) bool { +// If two map[string]any are given, all elements must match. +// If ignoreArrayOrder is true and both values are arrays, they are compared as sets +func matchesValue(av, bv any, ignoreArrayOrder bool) bool { if reflect.TypeOf(av) != reflect.TypeOf(bv) { return false } @@ -94,36 +165,75 @@ func matchesValue(av, bv interface{}) bool { if bt == at { return true } - case map[string]interface{}: - bt := bv.(map[string]interface{}) + case map[string]any: + bt := bv.(map[string]any) for key := range at { - if !matchesValue(at[key], bt[key]) { + if !matchesValue(at[key], bt[key], ignoreArrayOrder) { return false } } for key := range bt { - if !matchesValue(at[key], bt[key]) { + if !matchesValue(at[key], bt[key], ignoreArrayOrder) { return false } } return true - case []interface{}: - bt := bv.([]interface{}) + case []any: + bt := bv.([]any) if len(bt) != len(at) { return false } - for key := range at { - if !matchesValue(at[key], bt[key]) { + + if ignoreArrayOrder { + // Check if arrays have the same elements, regardless of order + // Create a map of element counts for each array + atCount := make(map[string]int) + btCount := make(map[string]int) + + // Count elements in first array + for _, v := range at { + // Convert element to JSON string for comparison + jsonBytes, err := json.Marshal(v) + if err != nil { + return false + } + jsonStr := string(jsonBytes) + atCount[jsonStr]++ + } + + // Count elements in second array + for _, v := range bt { + jsonBytes, err := json.Marshal(v) + if err != nil { + return false + } + jsonStr := string(jsonBytes) + btCount[jsonStr]++ + } + + // Compare counts + if len(atCount) != len(btCount) { return false } + + for k, v := range atCount { + if btCount[k] != v { + return false + } + } + + return true } - for key := range bt { - if !matchesValue(at[key], bt[key]) { + // Order matters, check each element in order + for key := range at { + if !matchesValue(at[key], bt[key], ignoreArrayOrder) { return false } } + return true } + return false } @@ -138,7 +248,7 @@ func matchesValue(av, bv interface{}) bool { var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1") -func makePath(path string, newPart interface{}) string { +func makePath(path string, newPart any) string { key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart)) if path == "" { return "/" + key @@ -150,11 +260,12 @@ func makePath(path string, newPart interface{}) string { } // diff returns the (recursive) difference between a and b as an array of JsonPatchOperations. -func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) ([]JsonPatchOperation, error) { +func diff(a, b map[string]any, path string, patch []JsonPatchOperation, strategy PatchStrategy, collections Collections) ([]JsonPatchOperation, error) { + //TODO: handle EnsureAbsent strategy for key, bv := range b { p := makePath(path, key) av, ok := a[key] - // value was added + // If the key is not present in a, add it if !ok { patch = append(patch, NewPatch("add", p, bv)) continue @@ -166,57 +277,67 @@ func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) } // Types are the same, compare values var err error - patch, err = handleValues(av, bv, p, patch) + patch, err = handleValues(av, bv, p, patch, strategy, collections) if err != nil { return nil, err } } - // Now add all deleted values as nil - for key := range a { - _, found := b[key] - if !found { - p := makePath(path, key) - - patch = append(patch, NewPatch("remove", p, nil)) - } - } + // Leaving this here for now, but the current thinking is that we never remove properties from objects. + // if strategy == PatchStrategyExactMatch { + // // Now add all deleted values as nil + // for key := range a { + // _, found := b[key] + // if !found { + // p := makePath(path, key) + // patch = append(patch, NewPatch("remove", p, nil)) + // } + // } + // } return patch, nil } -func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]JsonPatchOperation, error) { +func handleValues(av, bv any, p string, patch []JsonPatchOperation, strategy PatchStrategy, collections Collections) ([]JsonPatchOperation, error) { var err error + ignoreArrayOrder := !collections.isArray(p) switch at := av.(type) { - case map[string]interface{}: - bt := bv.(map[string]interface{}) - patch, err = diff(at, bt, p, patch) + case map[string]any: + bt := bv.(map[string]any) + patch, err = diff(at, bt, p, patch, strategy, collections) if err != nil { return nil, err } + return patch, nil case string, float64, bool: - if !matchesValue(av, bv) { + if !matchesValue(av, bv, ignoreArrayOrder) { patch = append(patch, NewPatch("replace", p, bv)) } - case []interface{}: - bt, ok := bv.([]interface{}) - if !ok { - // array replaced by non-array + return patch, nil + case []any: + bt, replaceWithOtherCollection := bv.([]any) + switch { + case !replaceWithOtherCollection: + // If the types are different, we replace the whole array patch = append(patch, NewPatch("replace", p, bv)) - } else if len(at) != len(bt) { - // arrays are not the same length - patch = append(patch, compareArray(at, bt, p)...) - - } else { + case collections.isArray(p) && len(at) != len(bt): + patch = append(patch, compareArray(at, bt, p, strategy, collections)...) + case collections.isArray(p) && len(at) == len(bt): + // If arrays have the same length, we can compare them element by element for i := range bt { - patch, err = handleValues(at[i], bt[i], makePath(p, i), patch) + patch, err = handleValues(at[i], bt[i], makePath(p, i), patch, strategy, collections) if err != nil { return nil, err } } + default: + // If this is not an array, we treat it as a set of values. + if !matchesValue(at, bt, true) { + patch = append(patch, compareArray(at, bt, p, strategy, collections)...) + } } case nil: switch bv.(type) { case nil: - // Both nil, fine. + // Both nil, fine. default: patch = append(patch, NewPatch("add", p, bv)) } @@ -227,47 +348,219 @@ func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]J } // compareArray generates remove and add operations for `av` and `bv`. -func compareArray(av, bv []interface{}, p string) []JsonPatchOperation { +func compareArray(av, bv []any, p string, strategy PatchStrategy, collections Collections) []JsonPatchOperation { retval := []JsonPatchOperation{} - // Find elements that need to be removed - processArray(av, bv, func(i int, value interface{}) { - retval = append(retval, NewPatch("remove", makePath(p, i), nil)) - }) - reversed := make([]JsonPatchOperation, len(retval)) - for i := 0; i < len(retval); i++ { - reversed[len(retval)-1-i] = retval[i] + switch { + case collections.isArray(p): + if strategy == PatchStrategyExactMatch { + // Find elements that need to be removed + processArray(av, bv, func(i int, value any) { + retval = append(retval, NewPatch("remove", makePath(p, i), nil)) + }, strategy) + reversed := make([]JsonPatchOperation, len(retval)) + for i := range retval { + reversed[len(retval)-1-i] = retval[i] + } + retval = reversed + } + + // Find elements that need to be added. + // NOTE we pass in `bv` then `av` so that processArray can find the missing elements. + processArray(bv, av, func(i int, value any) { + retval = append(retval, NewPatch("add", makePath(p, i), value)) + }, strategy) + case collections.isEntitySet(p): + if len(av) == len(bv) && matchesValue(av, bv, true) { + return retval + } + // TODO: removing is not tested yest! + removals := 0 + if strategy == PatchStrategyExactMatch { + // Find elements that need to be removed + elementsBeforeRemove := len(retval) + processIdentitySet(av, bv, p, func(i int, value any) { + retval = append(retval, NewPatch("remove", makePath(p, i), nil)) + }, func(ops []JsonPatchOperation) { + retval = append(retval, ops...) + }, strategy, collections) + removals = len(retval) - elementsBeforeRemove + reversed := make([]JsonPatchOperation, len(retval)) + for i := range retval { + reversed[len(retval)-1-i] = retval[i] + } + retval = reversed + } + offset := len(av) - removals + processIdentitySet(bv, av, p, func(i int, value any) { + retval = append(retval, NewPatch("add", makePath(p, i+offset), value)) + }, func(ops []JsonPatchOperation) { + retval = append(retval, ops...) + }, strategy, collections) + default: // default to set + if len(av) == len(bv) && matchesValue(av, bv, true) { + return retval + } + // TODO: removing is not tested yest! + // also we need to check for PatchStrategyEnsureAbsent + removals := 0 + if strategy == PatchStrategyExactMatch { + // Find elements that need to be removed + elementsBeforeRemove := len(retval) + processSet(av, bv, func(i int, value any) { retval = append(retval, NewPatch("remove", makePath(p, i), nil)) }) + removals = len(retval) - elementsBeforeRemove + reversed := make([]JsonPatchOperation, len(retval)) + for i := range retval { + reversed[len(retval)-1-i] = retval[i] + } + retval = reversed + } + offset := len(av) - removals + processSet(bv, av, func(i int, value any) { retval = append(retval, NewPatch("add", makePath(p, i+offset), value)) }) } - retval = reversed - // Find elements that need to be added. - // NOTE we pass in `bv` then `av` so that processArray can find the missing elements. - processArray(bv, av, func(i int, value interface{}) { - retval = append(retval, NewPatch("add", makePath(p, i), value)) - }) return retval } +func processSet(av, bv []any, applyOp func(i int, value any)) { + foundIndexes := make(map[int]struct{}, len(av)) + lookup := make(map[string]int) + + for i, v := range bv { + jsonBytes, err := json.Marshal(v) + if err != nil { + continue // Skip if we can't marshal + } + jsonStr := string(jsonBytes) + lookup[jsonStr] = i + } + + // Check each element in av + for i, v := range av { + jsonBytes, err := json.Marshal(v) + if err != nil { + applyOp(i, v) // If we can't marshal, treat it as not found + continue + } + + jsonStr := string(jsonBytes) + // If element exists in bv and we haven't seen all of them yet + if _, ok := lookup[jsonStr]; ok { + foundIndexes[i] = struct{}{} + } + } + + // Apply op for all elements in av that weren't found + for i, v := range av { + if _, ok := foundIndexes[i]; !ok { + applyOp(i, v) + } + } +} + +func processIdentitySet(av, bv []any, path string, applyOp func(i int, value any), replaceOps func(ops []JsonPatchOperation), strategy PatchStrategy, collections Collections) { + foundIndexes := make(map[int]struct{}, len(av)) + lookup := make(map[string]int) + + for i, v := range bv { + key, ok := collections.EntitySets.Get(Path(toJsonPath(path))) + if !ok { + continue // If we don't have a key for this path, skip + } + jsonBytes, err := json.Marshal(v.(map[string]any)[string(key)]) + if err != nil { + continue // Skip if we can't marshal + } + jsonStr := string(jsonBytes) + lookup[jsonStr] = i + } + + for i, v := range av { + key, ok := collections.EntitySets.Get(Path(toJsonPath(path))) + if !ok { + continue // If we don't have a key for this path, skip + } + jsonBytes, err := json.Marshal(v.(map[string]any)[string(key)]) + if err != nil { + applyOp(i, v) // If we can't marshal, treat it as not found + continue + } + + jsonStr := string(jsonBytes) + if index, ok := lookup[jsonStr]; ok { + foundIndexes[i] = struct{}{} + updateOps, err := handleValues(bv[index], v, fmt.Sprintf("%s/%d", path, lookup[jsonStr]), []JsonPatchOperation{}, strategy, collections) + if err != nil { + return + } + replaceOps(updateOps) + } + } + + for i, v := range av { + if _, ok := foundIndexes[i]; !ok { + applyOp(i, v) + } + } +} + // processArray processes `av` and `bv` calling `applyOp` whenever a value is absent. // It keeps track of which indexes have already had `applyOp` called for and automatically skips them so you can process duplicate objects correctly. -func processArray(av, bv []interface{}, applyOp func(i int, value interface{})) { +func processArray(av, bv []any, applyOp func(i int, value any), strategy PatchStrategy) { foundIndexes := make(map[int]struct{}, len(av)) - reverseFoundIndexes := make(map[int]struct{}, len(av)) - for i, v := range av { - for i2, v2 := range bv { - if _, ok := reverseFoundIndexes[i2]; ok { - // We already found this index. + switch strategy { + case PatchStrategyExactMatch: + reverseFoundIndexes := make(map[int]struct{}, len(bv)) + for i, v := range av { + for i2, v2 := range bv { + if _, ok := reverseFoundIndexes[i2]; ok { + continue + } + if reflect.DeepEqual(v, v2) { + foundIndexes[i] = struct{}{} + reverseFoundIndexes[i2] = struct{}{} + break + } + } + if _, ok := foundIndexes[i]; !ok { + applyOp(i, v) + } + } + case PatchStrategyEnsureExists: + offset := len(bv) + bvCounts := make(map[string]int) + bvSeen := make(map[string]int) // Track how many we've seen during processing + + for _, v := range bv { + jsonBytes, err := json.Marshal(v) + if err != nil { + continue // Skip if we can't marshal + } + jsonStr := string(jsonBytes) + bvCounts[jsonStr]++ + } + + for i, v := range av { + jsonBytes, err := json.Marshal(v) + if err != nil { + applyOp(i+offset, v) // If we can't marshal, treat it as not found continue } - if reflect.DeepEqual(v, v2) { - // Mark this index as found since it matches exactly. + + jsonStr := string(jsonBytes) + if bvCounts[jsonStr] > bvSeen[jsonStr] { foundIndexes[i] = struct{}{} - reverseFoundIndexes[i2] = struct{}{} - break + bvSeen[jsonStr]++ } } - if _, ok := foundIndexes[i]; !ok { - applyOp(i, v) + + for i, v := range av { + if _, ok := foundIndexes[i]; !ok { + applyOp(i+offset, v) + } } + return + case PatchStrategyEnsureAbsent: + return } } diff --git a/jsonpatch_array_at_root_test.go b/jsonpatch_array_at_root_test.go index 60f520f..f6bfef2 100644 --- a/jsonpatch_array_at_root_test.go +++ b/jsonpatch_array_at_root_test.go @@ -1,15 +1,12 @@ package jsonpatch import ( - "encoding/json" - "fmt" - "github.com/davecgh/go-spew/spew" - jsonpatch "github.com/evanphx/json-patch" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) -func TestJSONPatchCreate(t *testing.T) { +func TestJSONPatchCreate_ObjectRoot(t *testing.T) { cases := map[string]struct { a string b string @@ -22,6 +19,24 @@ func TestJSONPatchCreate(t *testing.T) { `{"items":[{"asdf":"qwerty"}]}`, `{"items":[{"asdf":"bla"},{"asdf":"zzz"}]}`, }, + } + + collections := Collections{ + Arrays: []string{"$.items"}, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + _, err := CreatePatch([]byte(tc.a), []byte(tc.b), collections, PatchStrategyExactMatch) + assert.NoError(t, err) + }) + } +} +func TestJSONPatchCreate_ArrayRoot(t *testing.T) { + cases := map[string]struct { + a string + b string + }{ "array": { `[{"asdf":"qwerty"}]`, `[{"asdf":"bla"},{"asdf":"zzz"}]`, @@ -36,24 +51,14 @@ func TestJSONPatchCreate(t *testing.T) { }, } + collections := Collections{ + Arrays: []string{"$"}, + } + for name, tc := range cases { t.Run(name, func(t *testing.T) { - patch, err := CreatePatch([]byte(tc.a), []byte(tc.b)) - assert.NoError(t, err) - - patchBytes, err := json.Marshal(patch) + _, err := CreatePatch([]byte(tc.a), []byte(tc.b), collections, PatchStrategyExactMatch) assert.NoError(t, err) - - fmt.Printf("%s\n", string(patchBytes)) - - p, err := jsonpatch.DecodePatch(patchBytes) - assert.NoError(t, err) - - res, err := p.Apply([]byte(tc.a)) - assert.NoError(t, err) - spew.Dump(res) - - assert.Equal(t, tc.b, string(res)) }) } -} \ No newline at end of file +} diff --git a/jsonpatch_array_test.go b/jsonpatch_array_test.go index d98ce73..65fc933 100644 --- a/jsonpatch_array_test.go +++ b/jsonpatch_array_test.go @@ -15,10 +15,14 @@ var ( arrayUpdated = `{ "persons": [{"name":"Ed"},{},{}] }` + + arrayTestCollections = Collections{ + Arrays: []string{"$.persons"}, + } ) -func TestArrayAddMultipleEmptyObjects(t *testing.T) { - patch, e := CreatePatch([]byte(arrayBase), []byte(arrayUpdated)) +func TestArrayAddMultipleEmptyObjectsExactMatch(t *testing.T) { + patch, e := CreatePatch([]byte(arrayBase), []byte(arrayUpdated), arrayTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) t.Log("Patch:", patch) assert.Equal(t, 1, len(patch), "they should be equal") @@ -27,11 +31,11 @@ func TestArrayAddMultipleEmptyObjects(t *testing.T) { change := patch[0] assert.Equal(t, "add", change.Operation, "they should be equal") assert.Equal(t, "/persons/2", change.Path, "they should be equal") - assert.Equal(t, map[string]interface{}{}, change.Value, "they should be equal") + assert.Equal(t, map[string]any{}, change.Value, "they should be equal") } -func TestArrayRemoveMultipleEmptyObjects(t *testing.T) { - patch, e := CreatePatch([]byte(arrayUpdated), []byte(arrayBase)) +func TestArrayRemoveMultipleEmptyObjectsExactMatch(t *testing.T) { + patch, e := CreatePatch([]byte(arrayUpdated), []byte(arrayBase), arrayTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) t.Log("Patch:", patch) assert.Equal(t, 1, len(patch), "they should be equal") @@ -56,7 +60,7 @@ var ( // TestArrayRemoveSpaceInbetween tests removing one blank item from a group blanks which is in between non blank items which also end with a blank item. This tests that the correct index is removed func TestArrayRemoveSpaceInbetween(t *testing.T) { t.Skip("This test fails. TODO change compareArray algorithm to match by index instead of by object equality") - patch, e := CreatePatch([]byte(arrayWithSpacesBase), []byte(arrayWithSpacesUpdated)) + patch, e := CreatePatch([]byte(arrayWithSpacesBase), []byte(arrayWithSpacesUpdated), arrayTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) t.Log("Patch:", patch) assert.Equal(t, 1, len(patch), "they should be equal") @@ -80,7 +84,7 @@ var ( // TestArrayRemoveMulti tests removing multi groups. This tests that the correct index is removed func TestArrayRemoveMulti(t *testing.T) { - patch, e := CreatePatch([]byte(arrayRemoveMultiBase), []byte(arrayRemoveMultisUpdated)) + patch, e := CreatePatch([]byte(arrayRemoveMultiBase), []byte(arrayRemoveMultisUpdated), arrayTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) t.Log("Patch:", patch) assert.Equal(t, 3, len(patch), "they should be equal") diff --git a/jsonpatch_complex_test.go b/jsonpatch_complex_test.go index 2a98871..47b2955 100644 --- a/jsonpatch_complex_test.go +++ b/jsonpatch_complex_test.go @@ -1,9 +1,9 @@ package jsonpatch import ( - "github.com/stretchr/testify/assert" - "sort" "testing" + + "github.com/stretchr/testify/assert" ) var complexBase = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":200, "g":"h", "i":"j"}}` @@ -13,13 +13,17 @@ var complexC = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":" var complexD = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"}, {"c3":"hello3", "d3":"foo3"} ], "e":{"f":200, "g":"h", "i":"j"}}` var complexE = `{"a":100, "b":[{"c1":"hello", "d1":"foo"},{"c2":"hello2", "d2":"foo2"} ], "e":{"f":200, "g":"h", "i":"j"}}` +var complex_test_collections = Collections{ + Arrays: []string{"$.b"}, +} + func TestComplexSame(t *testing.T) { - patch, e := CreatePatch([]byte(complexBase), []byte(complexBase)) + patch, e := CreatePatch([]byte(complexBase), []byte(complexBase), complex_test_collections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 0, len(patch), "they should be equal") } func TestComplexOneStringReplaceInArray(t *testing.T) { - patch, e := CreatePatch([]byte(complexBase), []byte(complexA)) + patch, e := CreatePatch([]byte(complexBase), []byte(complexA), complex_test_collections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 1, len(patch), "they should be equal") change := patch[0] @@ -29,7 +33,7 @@ func TestComplexOneStringReplaceInArray(t *testing.T) { } func TestComplexOneIntReplace(t *testing.T) { - patch, e := CreatePatch([]byte(complexBase), []byte(complexB)) + patch, e := CreatePatch([]byte(complexBase), []byte(complexB), complex_test_collections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 1, len(patch), "they should be equal") change := patch[0] @@ -40,49 +44,50 @@ func TestComplexOneIntReplace(t *testing.T) { } func TestComplexOneAdd(t *testing.T) { - patch, e := CreatePatch([]byte(complexBase), []byte(complexC)) + patch, e := CreatePatch([]byte(complexBase), []byte(complexC), complex_test_collections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 1, len(patch), "they should be equal") change := patch[0] assert.Equal(t, "add", change.Operation, "they should be equal") assert.Equal(t, "/k", change.Path, "they should be equal") - a := make(map[string]interface{}) - b := make(map[string]interface{}) + a := make(map[string]any) + b := make(map[string]any) a["l"] = "m" b["l"] = "o" - expected := []interface{}{a, b} + expected := []any{a, b} assert.Equal(t, expected, change.Value, "they should be equal") } func TestComplexOneAddToArray(t *testing.T) { - patch, e := CreatePatch([]byte(complexBase), []byte(complexC)) + patch, e := CreatePatch([]byte(complexBase), []byte(complexC), complex_test_collections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 1, len(patch), "they should be equal") change := patch[0] assert.Equal(t, "add", change.Operation, "they should be equal") assert.Equal(t, "/k", change.Path, "they should be equal") - a := make(map[string]interface{}) - b := make(map[string]interface{}) + a := make(map[string]any) + b := make(map[string]any) a["l"] = "m" b["l"] = "o" - expected := []interface{}{a, b} + expected := []any{a, b} assert.Equal(t, expected, change.Value, "they should be equal") } +// We never remove keys from objects func TestComplexVsEmpty(t *testing.T) { - patch, e := CreatePatch([]byte(complexBase), []byte(empty)) + patch, e := CreatePatch([]byte(complexBase), []byte(empty), complex_test_collections, PatchStrategyExactMatch) assert.NoError(t, e) - assert.Equal(t, 3, len(patch), "they should be equal") - sort.Sort(ByPath(patch)) - change := patch[0] - assert.Equal(t, "remove", change.Operation, "they should be equal") - assert.Equal(t, "/a", change.Path, "they should be equal") - - change = patch[1] - assert.Equal(t, "remove", change.Operation, "they should be equal") - assert.Equal(t, "/b", change.Path, "they should be equal") - - change = patch[2] - assert.Equal(t, "remove", change.Operation, "they should be equal") - assert.Equal(t, "/e", change.Path, "they should be equal") + assert.Equal(t, 0, len(patch), "they should be equal") + // sort.Sort(ByPath(patch)) + // change := patch[0] + // assert.Equal(t, "remove", change.Operation, "they should be equal") + // assert.Equal(t, "/a", change.Path, "they should be equal") + // + // change = patch[1] + // assert.Equal(t, "remove", change.Operation, "they should be equal") + // assert.Equal(t, "/b", change.Path, "they should be equal") + // + // change = patch[2] + // assert.Equal(t, "remove", change.Operation, "they should be equal") + // assert.Equal(t, "/e", change.Path, "they should be equal") } diff --git a/jsonpatch_entity_set_test.go b/jsonpatch_entity_set_test.go new file mode 100644 index 0000000..e8fe7ca --- /dev/null +++ b/jsonpatch_entity_set_test.go @@ -0,0 +1,107 @@ +package jsonpatch + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var simpleObjEntitySet = `{"a":100, "t":[{"k":1, "v":1},{"k":2, "v":2}]}` +var simpleObjAddEntitySetItem = `{"t":[{"k":3, "v":3}]}` +var simpleObjModifyEntitySetItem = `{"t":[{"k":2, "v":3}]}` +var simpleObjAddDuplicateEntitySetItem = `{"t":[{"k":2, "v":2}]}` +var complexNextedEntitySet = `{ + "a":100, + "t":[ + {"k":1, + "v":[ + {"nk":11, "c":"x", "d":[1,2]}, + {"nk":22, "c":"y", "d":[3,4]} + ] + }, + {"k":2, + "v":[ + {"nk":33, "c":"z", "d":[5,6]} + ] + } + ]}` +var complexNextedEntitySetModifyItem = `{ + "t":[ + {"k":2, + "v":[ + {"nk":33, "c":"zz", "d":[7,8]} + ] + } + ]}` + +var entitySetTestCollections = Collections{ + EntitySets: EntitySets{ + Path("$.t"): Key("k"), + Path("$.t[*].v"): Key("nk"), + }, + Arrays: []string{}, // No arrays in this test, only sets +} + +func TestCreatePatch_AddItemToEntitySet_InEnsureExistsMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjEntitySet), []byte(simpleObjAddEntitySetItem), entitySetTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/t/2", change.Path, "they should be equal") + var expected = map[string]any{"k": float64(3), "v": float64(3)} + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToEntitySet_InExactMatchMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjEntitySet), []byte(simpleObjAddEntitySetItem), entitySetTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 3, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/t/1", change.Path, "they should be equal") + change = patch[1] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/t/0", change.Path, "they should be equal") + change = patch[2] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/t/0", change.Path, "they should be equal") + var expected = map[string]any{"k": float64(3), "v": float64(3)} + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_EntitySet_ModifyItem_GeneratesReplaceOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjEntitySet), []byte(simpleObjModifyEntitySetItem), entitySetTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "replace", change.Operation, "they should be equal") + assert.Equal(t, "/t/1/v", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_EntitySet_AddDuplicateItem_GeneratesNoOperations(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjEntitySet), []byte(simpleObjAddDuplicateEntitySetItem), entitySetTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 0, len(patch), "they should be equal") +} + +func TestCreatePatch_ComplexNestedEntitySet_ModifyItem_GeneratesReplaceOperation(t *testing.T) { + patch, err := CreatePatch([]byte(complexNextedEntitySet), []byte(complexNextedEntitySetModifyItem), entitySetTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 3, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "replace", change.Operation, "they should be equal") + assert.Equal(t, "/t/1/v/0/c", change.Path, "they should be equal") + assert.Equal(t, "zz", change.Value, "they should be equal") + change = patch[1] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/t/1/v/0/d/2", change.Path, "they should be equal") + assert.Equal(t, float64(7), change.Value, "they should be equal") + change = patch[2] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/t/1/v/0/d/3", change.Path, "they should be equal") + assert.Equal(t, float64(8), change.Value, "they should be equal") +} diff --git a/jsonpatch_geojson_test.go b/jsonpatch_geojson_test.go index 6a92da0..3bec3ef 100644 --- a/jsonpatch_geojson_test.go +++ b/jsonpatch_geojson_test.go @@ -1,27 +1,31 @@ package jsonpatch import ( - "github.com/stretchr/testify/assert" "sort" "testing" + + "github.com/stretchr/testify/assert" ) var point = `{"type":"Point", "coordinates":[0.0, 1.0]}` var lineString = `{"type":"LineString", "coordinates":[[0.0, 1.0], [2.0, 3.0]]}` +var geotestCollections = Collections{ + Arrays: []string{"$.coordinates"}, +} func TestPointLineStringReplace(t *testing.T) { - patch, e := CreatePatch([]byte(point), []byte(lineString)) + patch, e := CreatePatch([]byte(point), []byte(lineString), geotestCollections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 3, "they should be equal") sort.Sort(ByPath(patch)) change := patch[0] assert.Equal(t, change.Operation, "replace", "they should be equal") assert.Equal(t, change.Path, "/coordinates/0", "they should be equal") - assert.Equal(t, change.Value, []interface{}{0.0, 1.0}, "they should be equal") + assert.Equal(t, change.Value, []any{0.0, 1.0}, "they should be equal") change = patch[1] assert.Equal(t, change.Operation, "replace", "they should be equal") assert.Equal(t, change.Path, "/coordinates/1", "they should be equal") - assert.Equal(t, change.Value, []interface{}{2.0, 3.0}, "they should be equal") + assert.Equal(t, change.Value, []any{2.0, 3.0}, "they should be equal") change = patch[2] assert.Equal(t, change.Operation, "replace", "they should be equal") assert.Equal(t, change.Path, "/type", "they should be equal") @@ -29,7 +33,7 @@ func TestPointLineStringReplace(t *testing.T) { } func TestLineStringPointReplace(t *testing.T) { - patch, e := CreatePatch([]byte(lineString), []byte(point)) + patch, e := CreatePatch([]byte(lineString), []byte(point), geotestCollections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 3, "they should be equal") sort.Sort(ByPath(patch)) diff --git a/jsonpatch_hypercomplex_test.go b/jsonpatch_hypercomplex_test.go index f34423b..23abaff 100644 --- a/jsonpatch_hypercomplex_test.go +++ b/jsonpatch_hypercomplex_test.go @@ -1,9 +1,10 @@ package jsonpatch import ( - "github.com/stretchr/testify/assert" "sort" "testing" + + "github.com/stretchr/testify/assert" ) var hyperComplexBase = ` @@ -154,14 +155,18 @@ var hyperComplexA = ` ] }` +var hyperComplexTestCollections = Collections{ + Arrays: []string{"$.goods", "$.goods[*].batters.batter", "$.goods[*].topping"}, +} + func TestHyperComplexSame(t *testing.T) { - patch, e := CreatePatch([]byte(hyperComplexBase), []byte(hyperComplexBase)) + patch, e := CreatePatch([]byte(hyperComplexBase), []byte(hyperComplexBase), hyperComplexTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 0, "they should be equal") } func TestHyperComplexBoolReplace(t *testing.T) { - patch, e := CreatePatch([]byte(hyperComplexBase), []byte(hyperComplexA)) + patch, e := CreatePatch([]byte(hyperComplexBase), []byte(hyperComplexA), hyperComplexTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 3, len(patch), "they should be equal") sort.Sort(ByPath(patch)) @@ -173,7 +178,7 @@ func TestHyperComplexBoolReplace(t *testing.T) { change = patch[1] assert.Equal(t, "add", change.Operation, "they should be equal") assert.Equal(t, "/goods/2/batters/batter/2", change.Path, "they should be equal") - assert.Equal(t, map[string]interface{}{"id": "1003", "type": "Vanilla"}, change.Value, "they should be equal") + assert.Equal(t, map[string]any{"id": "1003", "type": "Vanilla"}, change.Value, "they should be equal") change = patch[2] assert.Equal(t, change.Operation, "remove", "they should be equal") assert.Equal(t, change.Path, "/goods/2/topping/2", "they should be equal") diff --git a/jsonpatch_object_test.go b/jsonpatch_object_test.go new file mode 100644 index 0000000..14f9760 --- /dev/null +++ b/jsonpatch_object_test.go @@ -0,0 +1,53 @@ +package jsonpatch + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var simpleObj = `{"a":100, "b":20}` +var simpleObjModifyProp = `{"b":250}` +var simpleObjAddProp = `{"c":"hello"}` + +func TestCreatePatch_ModifyProperty_GeneratesReplaceOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObj), []byte(simpleObjModifyProp), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "replace", change.Operation, "they should be equal") + assert.Equal(t, "/b", change.Path, "they should be equal") + var expected float64 = 250 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddProperty_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObj), []byte(simpleObjAddProp), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/c", change.Path, "they should be equal") + assert.Equal(t, "hello", change.Value, "they should be equal") +} + +func TestCreatePatch_NestedObject_ModifyProperty_GeneratesReplaceOperation(t *testing.T) { + patch, err := CreatePatch([]byte(nestedObj), []byte(nestedObjModifyProp), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "replace", change.Operation, "they should be equal") + assert.Equal(t, "/b/c", change.Path, "they should be equal") + var expected float64 = 250 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_NestedObject_AddProperty_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(nestedObj), []byte(nestedObjAddProp), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/d", change.Path, "they should be equal") + assert.Equal(t, "hello", change.Value, "they should be equal") +} diff --git a/jsonpatch_set_test.go b/jsonpatch_set_test.go new file mode 100644 index 0000000..634cffd --- /dev/null +++ b/jsonpatch_set_test.go @@ -0,0 +1,218 @@ +package jsonpatch + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var simpleObjEmtpyPrmitiveSet = `{"a":100, "b":[]}` +var simpleObjPrimitiveSetWithOneItem = `{"a":100, "b":[1]}` +var simpleObjPrimitiveSetWithMultipleItems = `{"a":100, "b":[1,2]}` +var simpleObjAddSingleItemToPrimitiveSet = `{"b":[3]}` +var simpleObjAddMultipleItemsToPrimitiveSet = `{"b":[3,4]}` +var simpleObjAddDuplicateItemToPrimitiveSet = `{"b":[2]}` +var simpleObjSingletonObjectSet = `{"a":100, "b":[{"c":1}]}` +var simpleObjAddObjectSetItem = `{"b":[{"c":2}]}` +var simpleObjAddDuplicateObjectSetItem = `{"b":[{"c":1}]}` + +var nestedObj = `{"a":100, "b":{"c":200}}` +var nestedObjModifyProp = `{"b":{"c":250}}` +var nestedObjAddProp = `{"b":{"d":"hello"}}` +var nestedObjPrimitiveSet = `{"a":100, "b":{"c":[200]}}` +var nestedObjAddPrimitiveSetItem = `{"b":{"c":[250]}}` + +var setTestCollections = Collections{ + EntitySets: EntitySets{}, + Arrays: []string{}, // No arrays in this test, only sets +} + +func TestCreatePatch_AddItemToEmptyPrimitiveSetInEnsureExistsMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjEmtpyPrmitiveSet), []byte(simpleObjAddSingleItemToPrimitiveSet), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToEmptyPrimitiveSetInEnsureExactMatchMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjEmtpyPrmitiveSet), []byte(simpleObjAddSingleItemToPrimitiveSet), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToPrimitiveSetWithOneItemInEnsureExistsMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithOneItem), []byte(simpleObjAddSingleItemToPrimitiveSet), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/1", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToPrimitiveSetWithOneItemInExactMatchMode_GeneratesARemoveAndAnAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithOneItem), []byte(simpleObjAddSingleItemToPrimitiveSet), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 2, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + change = patch[1] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToPrimitiveSetWithMultipleItems_InEnsureExistsMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithMultipleItems), []byte(simpleObjAddSingleItemToPrimitiveSet), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/2", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToPrimitiveSetWithMultipleItems_InExactMatchMode_GeneratesTwoRemovesAndOneAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithMultipleItems), []byte(simpleObjAddSingleItemToPrimitiveSet), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 3, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/1", change.Path, "they should be equal") + change = patch[1] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + change = patch[2] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddMultipleItemsToPrimitiveSetWithMultipleItems_InEnsureExistsMode_GeneratesTwoAddOperations(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithMultipleItems), []byte(simpleObjAddMultipleItemsToPrimitiveSet), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 2, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/2", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") + change = patch[1] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/3", change.Path, "they should be equal") + expected = 4 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddMultipleItemsToPrimitiveSetWithMultipleItems_InExactMatchMode_GeneratesTwoRemovesAndTwoAddOperations(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithMultipleItems), []byte(simpleObjAddMultipleItemsToPrimitiveSet), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 4, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/1", change.Path, "they should be equal") + change = patch[1] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + change = patch[2] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + var expected float64 = 3 + assert.Equal(t, expected, change.Value, "they should be equal") + change = patch[3] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/1", change.Path, "they should be equal") + expected = 4 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddDuplicateItemToPrimitiveSetWithOneMultipleItems_InEnsureExistsMode_GeneratesNoOperations(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithMultipleItems), []byte(simpleObjAddDuplicateItemToPrimitiveSet), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 0, len(patch), "they should be equal") +} + +func TestCreatePatch_AddDuplicateItemToPrimitiveSetWithOneMultipleItems_InExactMatchMode_GeneratesARemoveOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjPrimitiveSetWithMultipleItems), []byte(simpleObjAddDuplicateItemToPrimitiveSet), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") +} + +func TestCreatePatch_AddItemToPrimitiveSetInNestedObject_InEnsureExistsMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(nestedObjPrimitiveSet), []byte(nestedObjAddPrimitiveSetItem), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/c/1", change.Path, "they should be equal") + var expected float64 = 250 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToPrimitiveSetInNestedObject_InExactMatchMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(nestedObjPrimitiveSet), []byte(nestedObjAddPrimitiveSetItem), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 2, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/c/0", change.Path, "they should be equal") + change = patch[1] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/c/0", change.Path, "they should be equal") + var expected float64 = 250 + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToObjectSetWithOneItem_InEnsureExistsMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjSingletonObjectSet), []byte(simpleObjAddObjectSetItem), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 1, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/1", change.Path, "they should be equal") + var expected = map[string]any{"c": float64(2)} + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddItemToObjectSetWithOneItem_InExactMatchMode_GeneratesAddOperation(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjSingletonObjectSet), []byte(simpleObjAddObjectSetItem), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 2, len(patch), "they should be equal") + change := patch[0] + assert.Equal(t, "remove", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + change = patch[1] + assert.Equal(t, "add", change.Operation, "they should be equal") + assert.Equal(t, "/b/0", change.Path, "they should be equal") + var expected = map[string]any{"c": float64(2)} + assert.Equal(t, expected, change.Value, "they should be equal") +} + +func TestCreatePatch_AddDuplicateItemToObjectSetWithOneItem_InEnsureExistsMode_GeneratesNoOperations(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjSingletonObjectSet), []byte(simpleObjAddDuplicateObjectSetItem), setTestCollections, PatchStrategyEnsureExists) + assert.NoError(t, err) + assert.Equal(t, 0, len(patch), "they should be equal") +} + +func TestCreatePatch_AddDuplicateItemToObjectSetWithOneItem_InExactMatchMode_GeneratesNoOperations(t *testing.T) { + patch, err := CreatePatch([]byte(simpleObjSingletonObjectSet), []byte(simpleObjAddDuplicateObjectSetItem), setTestCollections, PatchStrategyExactMatch) + assert.NoError(t, err) + assert.Equal(t, 0, len(patch), "they should be equal") +} diff --git a/jsonpatch_simple_test.go b/jsonpatch_simple_test.go index 1a6ec29..3b037fe 100644 --- a/jsonpatch_simple_test.go +++ b/jsonpatch_simple_test.go @@ -1,7 +1,6 @@ package jsonpatch import ( - "sort" "testing" "github.com/stretchr/testify/assert" @@ -17,7 +16,7 @@ var simpleG = `{"a":100, "b":null, "d":"foo"}` var empty = `{}` func TestOneNullReplace(t *testing.T) { - patch, e := CreatePatch([]byte(simplef), []byte(simpleG)) + patch, e := CreatePatch([]byte(simplef), []byte(simpleG), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 1, "they should be equal") change := patch[0] @@ -27,13 +26,13 @@ func TestOneNullReplace(t *testing.T) { } func TestSame(t *testing.T) { - patch, e := CreatePatch([]byte(simpleA), []byte(simpleA)) + patch, e := CreatePatch([]byte(simpleA), []byte(simpleA), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 0, "they should be equal") } func TestOneStringReplace(t *testing.T) { - patch, e := CreatePatch([]byte(simpleA), []byte(simpleB)) + patch, e := CreatePatch([]byte(simpleA), []byte(simpleB), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 1, "they should be equal") change := patch[0] @@ -43,7 +42,7 @@ func TestOneStringReplace(t *testing.T) { } func TestOneIntReplace(t *testing.T) { - patch, e := CreatePatch([]byte(simpleA), []byte(simpleC)) + patch, e := CreatePatch([]byte(simpleA), []byte(simpleC), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 1, "they should be equal") change := patch[0] @@ -54,7 +53,7 @@ func TestOneIntReplace(t *testing.T) { } func TestOneAdd(t *testing.T) { - patch, e := CreatePatch([]byte(simpleA), []byte(simpleD)) + patch, e := CreatePatch([]byte(simpleA), []byte(simpleD), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, len(patch), 1, "they should be equal") change := patch[0] @@ -63,58 +62,60 @@ func TestOneAdd(t *testing.T) { assert.Equal(t, change.Value, "foo", "they should be equal") } +// We never remove properties from objects func TestOneRemove(t *testing.T) { - patch, e := CreatePatch([]byte(simpleA), []byte(simpleE)) + patch, e := CreatePatch([]byte(simpleA), []byte(simpleE), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) - assert.Equal(t, len(patch), 1, "they should be equal") - change := patch[0] - assert.Equal(t, change.Operation, "remove", "they should be equal") - assert.Equal(t, change.Path, "/c", "they should be equal") - assert.Equal(t, change.Value, nil, "they should be equal") + assert.Equal(t, len(patch), 0, "they should be equal") + // change := patch[0] + // assert.Equal(t, change.Operation, "remove", "they should be equal") + // assert.Equal(t, change.Path, "/c", "they should be equal") + // assert.Equal(t, change.Value, nil, "they should be equal") } +// We never remove properties from objects func TestVsEmpty(t *testing.T) { - patch, e := CreatePatch([]byte(simpleA), []byte(empty)) + patch, e := CreatePatch([]byte(simpleA), []byte(empty), Collections{}, PatchStrategyExactMatch) assert.NoError(t, e) - assert.Equal(t, len(patch), 3, "they should be equal") - sort.Sort(ByPath(patch)) - change := patch[0] - assert.Equal(t, change.Operation, "remove", "they should be equal") - assert.Equal(t, change.Path, "/a", "they should be equal") - - change = patch[1] - assert.Equal(t, change.Operation, "remove", "they should be equal") - assert.Equal(t, change.Path, "/b", "they should be equal") - - change = patch[2] - assert.Equal(t, change.Operation, "remove", "they should be equal") - assert.Equal(t, change.Path, "/c", "they should be equal") + assert.Equal(t, len(patch), 0, "they should be equal") + // sort.Sort(ByPath(patch)) + // change := patch[0] + // assert.Equal(t, change.Operation, "remove", "they should be equal") + // assert.Equal(t, change.Path, "/a", "they should be equal") + // + // change = patch[1] + // assert.Equal(t, change.Operation, "remove", "they should be equal") + // assert.Equal(t, change.Path, "/b", "they should be equal") + // + // change = patch[2] + // assert.Equal(t, change.Operation, "remove", "they should be equal") + // assert.Equal(t, change.Path, "/c", "they should be equal") } func BenchmarkBigArrays(b *testing.B) { - var a1, a2 []interface{} - a1 = make([]interface{}, 100) - a2 = make([]interface{}, 101) + var a1, a2 []any + a1 = make([]any, 100) + a2 = make([]any, 101) - for i := 0; i < 100; i++ { + for i := range 100 { a1[i] = i a2[i+1] = i } for i := 0; i < b.N; i++ { - compareArray(a1, a2, "/") + compareArray(a1, a2, "/", PatchStrategyExactMatch, Collections{}) } } func BenchmarkBigArrays2(b *testing.B) { - var a1, a2 []interface{} - a1 = make([]interface{}, 100) - a2 = make([]interface{}, 101) + var a1, a2 []any + a1 = make([]any, 100) + a2 = make([]any, 101) - for i := 0; i < 100; i++ { + for i := range 100 { a1[i] = i a2[i] = i } for i := 0; i < b.N; i++ { - compareArray(a1, a2, "/") + compareArray(a1, a2, "/", PatchStrategyExactMatch, Collections{}) } } diff --git a/jsonpatch_supercomplex_test.go b/jsonpatch_supercomplex_test.go index 6e98f3a..1912acb 100644 --- a/jsonpatch_supercomplex_test.go +++ b/jsonpatch_supercomplex_test.go @@ -1,506 +1,512 @@ package jsonpatch import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) var superComplexBase = ` -{ - "annotations": { - "annotation": [ - { - "name": "version", - "value": "8" - }, - { - "name": "versionTag", - "value": "Published on May 13, 2015 at 8:48pm (MST)" - } - ] - }, - "attributes": { - "attribute-key": [ - { - "id": "3b05c943-d81a-436f-b242-8b519e7a6f30", - "properties": { - "visible": true - } - }, - { - "id": "d794c7ee-2a4b-4da4-bba7-e8b973d50c4b", - "properties": { - "visible": true - } - }, - { - "id": "a0259458-517c-480f-9f04-9b54b1b2af1f", - "properties": { - "visible": true - } - }, - { - "id": "9415f39d-c396-4458-9019-fc076c847964", - "properties": { - "visible": true - } - }, - { - "id": "0a2e49a9-8989-42fb-97da-cc66334f828b", - "properties": { - "visible": true - } - }, - { - "id": "27f5f14a-ea97-4feb-b22a-6ff754a31212", - "properties": { - "visible": true - } - }, - { - "id": "6f810508-4615-4fd0-9e87-80f9c94f9ad8", - "properties": { - "visible": true - } - }, - { - "id": "3451b1b2-7365-455c-8bb1-0b464d4d3ba1", - "properties": { - "visible": true - } - }, - { - "id": "a82ec957-8c26-41ea-8af6-6dd75c384801", - "properties": { - "visible": true - } - }, - { - "id": "736c5496-9a6e-4a82-aa00-456725796432", - "properties": { - "visible": true - } - }, - { - "id": "2d428b3c-9d3b-4ec1-bf98-e00673599d60", - "properties": { - "visible": true - } - }, - { - "id": "68566ebb-811d-4337-aba9-a8a8baf90e4b", - "properties": { - "visible": true - } - }, - { - "id": "ca88bab1-a1ea-40cc-8f96-96d1e9f1217d", - "properties": { - "visible": true - } - }, - { - "id": "c63a12c8-542d-47f3-bee1-30b5fe2b0690", - "properties": { - "visible": true - } - }, - { - "id": "cbd9e3bc-6a49-432a-a906-b1674c1de24c", - "properties": { - "visible": true - } - }, - { - "id": "03262f07-8a15-416d-a3f5-e2bf561c78f9", - "properties": { - "visible": true - } - }, - { - "id": "e5c93b87-83fc-45b6-b4d5-bf1e3f523075", - "properties": { - "visible": true - } - }, - { - "id": "72260ac5-3d51-49d7-bb31-f794dd129f1c", - "properties": { - "visible": true - } - }, - { - "id": "d856bde1-1b42-4935-9bee-c37e886c9ecf", - "properties": { - "visible": true - } - }, - { - "id": "62380509-bedf-4134-95c3-77ff377a4a6a", - "properties": { - "visible": true - } - }, - { - "id": "f4ed5ac9-b386-49a6-a0a0-6f3341ce9021", - "properties": { - "visible": true - } - }, - { - "id": "528d2bd2-87fe-4a49-954a-c93a03256929", - "properties": { - "visible": true - } - }, - { - "id": "ff8951f1-61a7-416b-9223-fac4bb6dac50", - "properties": { - "visible": true - } - }, - { - "id": "95c2b011-d782-4042-8a07-6aa4a5765c2e", - "properties": { - "visible": true - } - }, - { - "id": "dbe5837b-0624-4a05-91f3-67b5bd9b812a", - "properties": { - "visible": true - } - }, - { - "id": "13f198ed-82ab-4e51-8144-bfaa5bf77fd5", - "properties": { - "visible": true - } - }, - { - "id": "025312eb-12b6-47e6-9750-0fb31ddc2111", - "properties": { - "visible": true - } - }, - { - "id": "24292d58-db66-4ef3-8f4f-005d7b719433", - "properties": { - "visible": true - } - }, - { - "id": "22e5b5c4-821c-413a-a5b1-ab866d9a03bb", - "properties": { - "visible": true - } - }, - { - "id": "2fde0aac-df89-403d-998e-854b949c7b57", - "properties": { - "visible": true - } - }, - { - "id": "8b576876-5c16-4178-805e-24984c24fac3", - "properties": { - "visible": true - } - }, - { - "id": "415b7d2a-b362-4f1e-b83a-927802328ecb", - "properties": { - "visible": true - } - }, - { - "id": "8ef24fc2-ab25-4f22-9d9f-61902b49dc01", - "properties": { - "visible": true - } - }, - { - "id": "2299b09e-9f8e-4b79-a55c-a7edacde2c85", - "properties": { - "visible": true - } - }, - { - "id": "bf506538-f438-425c-be85-5aa2f9b075b8", - "properties": { - "visible": true - } - }, - { - "id": "2b501dc6-799d-4675-9144-fac77c50c57c", - "properties": { - "visible": true - } - }, - { - "id": "c0446da1-e069-417e-bd5a-34edcd028edc", - "properties": { - "visible": true - } - } - ] - } -}` + { + "annotations": { + "annotation": [ + { + "name": "version", + "value": "8" + }, + { + "name": "versionTag", + "value": "Published on May 13, 2015 at 8:48pm (MST)" + } + ] + }, + "attributes": { + "attribute-key": [ + { + "id": "3b05c943-d81a-436f-b242-8b519e7a6f30", + "properties": { + "visible": true + } + }, + { + "id": "d794c7ee-2a4b-4da4-bba7-e8b973d50c4b", + "properties": { + "visible": true + } + }, + { + "id": "a0259458-517c-480f-9f04-9b54b1b2af1f", + "properties": { + "visible": true + } + }, + { + "id": "9415f39d-c396-4458-9019-fc076c847964", + "properties": { + "visible": true + } + }, + { + "id": "0a2e49a9-8989-42fb-97da-cc66334f828b", + "properties": { + "visible": true + } + }, + { + "id": "27f5f14a-ea97-4feb-b22a-6ff754a31212", + "properties": { + "visible": true + } + }, + { + "id": "6f810508-4615-4fd0-9e87-80f9c94f9ad8", + "properties": { + "visible": true + } + }, + { + "id": "3451b1b2-7365-455c-8bb1-0b464d4d3ba1", + "properties": { + "visible": true + } + }, + { + "id": "a82ec957-8c26-41ea-8af6-6dd75c384801", + "properties": { + "visible": true + } + }, + { + "id": "736c5496-9a6e-4a82-aa00-456725796432", + "properties": { + "visible": true + } + }, + { + "id": "2d428b3c-9d3b-4ec1-bf98-e00673599d60", + "properties": { + "visible": true + } + }, + { + "id": "68566ebb-811d-4337-aba9-a8a8baf90e4b", + "properties": { + "visible": true + } + }, + { + "id": "ca88bab1-a1ea-40cc-8f96-96d1e9f1217d", + "properties": { + "visible": true + } + }, + { + "id": "c63a12c8-542d-47f3-bee1-30b5fe2b0690", + "properties": { + "visible": true + } + }, + { + "id": "cbd9e3bc-6a49-432a-a906-b1674c1de24c", + "properties": { + "visible": true + } + }, + { + "id": "03262f07-8a15-416d-a3f5-e2bf561c78f9", + "properties": { + "visible": true + } + }, + { + "id": "e5c93b87-83fc-45b6-b4d5-bf1e3f523075", + "properties": { + "visible": true + } + }, + { + "id": "72260ac5-3d51-49d7-bb31-f794dd129f1c", + "properties": { + "visible": true + } + }, + { + "id": "d856bde1-1b42-4935-9bee-c37e886c9ecf", + "properties": { + "visible": true + } + }, + { + "id": "62380509-bedf-4134-95c3-77ff377a4a6a", + "properties": { + "visible": true + } + }, + { + "id": "f4ed5ac9-b386-49a6-a0a0-6f3341ce9021", + "properties": { + "visible": true + } + }, + { + "id": "528d2bd2-87fe-4a49-954a-c93a03256929", + "properties": { + "visible": true + } + }, + { + "id": "ff8951f1-61a7-416b-9223-fac4bb6dac50", + "properties": { + "visible": true + } + }, + { + "id": "95c2b011-d782-4042-8a07-6aa4a5765c2e", + "properties": { + "visible": true + } + }, + { + "id": "dbe5837b-0624-4a05-91f3-67b5bd9b812a", + "properties": { + "visible": true + } + }, + { + "id": "13f198ed-82ab-4e51-8144-bfaa5bf77fd5", + "properties": { + "visible": true + } + }, + { + "id": "025312eb-12b6-47e6-9750-0fb31ddc2111", + "properties": { + "visible": true + } + }, + { + "id": "24292d58-db66-4ef3-8f4f-005d7b719433", + "properties": { + "visible": true + } + }, + { + "id": "22e5b5c4-821c-413a-a5b1-ab866d9a03bb", + "properties": { + "visible": true + } + }, + { + "id": "2fde0aac-df89-403d-998e-854b949c7b57", + "properties": { + "visible": true + } + }, + { + "id": "8b576876-5c16-4178-805e-24984c24fac3", + "properties": { + "visible": true + } + }, + { + "id": "415b7d2a-b362-4f1e-b83a-927802328ecb", + "properties": { + "visible": true + } + }, + { + "id": "8ef24fc2-ab25-4f22-9d9f-61902b49dc01", + "properties": { + "visible": true + } + }, + { + "id": "2299b09e-9f8e-4b79-a55c-a7edacde2c85", + "properties": { + "visible": true + } + }, + { + "id": "bf506538-f438-425c-be85-5aa2f9b075b8", + "properties": { + "visible": true + } + }, + { + "id": "2b501dc6-799d-4675-9144-fac77c50c57c", + "properties": { + "visible": true + } + }, + { + "id": "c0446da1-e069-417e-bd5a-34edcd028edc", + "properties": { + "visible": true + } + } + ] + } + }` var superComplexA = ` -{ - "annotations": { - "annotation": [ - { - "name": "version", - "value": "8" - }, - { - "name": "versionTag", - "value": "Published on May 13, 2015 at 8:48pm (MST)" - } - ] - }, - "attributes": { - "attribute-key": [ - { - "id": "3b05c943-d81a-436f-b242-8b519e7a6f30", - "properties": { - "visible": true - } - }, - { - "id": "d794c7ee-2a4b-4da4-bba7-e8b973d50c4b", - "properties": { - "visible": true - } - }, - { - "id": "a0259458-517c-480f-9f04-9b54b1b2af1f", - "properties": { - "visible": true - } - }, - { - "id": "9415f39d-c396-4458-9019-fc076c847964", - "properties": { - "visible": true - } - }, - { - "id": "0a2e49a9-8989-42fb-97da-cc66334f828b", - "properties": { - "visible": true - } - }, - { - "id": "27f5f14a-ea97-4feb-b22a-6ff754a31212", - "properties": { - "visible": true - } - }, - { - "id": "6f810508-4615-4fd0-9e87-80f9c94f9ad8", - "properties": { - "visible": true - } - }, - { - "id": "3451b1b2-7365-455c-8bb1-0b464d4d3ba1", - "properties": { - "visible": true - } - }, - { - "id": "a82ec957-8c26-41ea-8af6-6dd75c384801", - "properties": { - "visible": true - } - }, - { - "id": "736c5496-9a6e-4a82-aa00-456725796432", - "properties": { - "visible": true - } - }, - { - "id": "2d428b3c-9d3b-4ec1-bf98-e00673599d60", - "properties": { - "visible": true - } - }, - { - "id": "68566ebb-811d-4337-aba9-a8a8baf90e4b", - "properties": { - "visible": true - } - }, - { - "id": "ca88bab1-a1ea-40cc-8f96-96d1e9f1217d", - "properties": { - "visible": true - } - }, - { - "id": "c63a12c8-542d-47f3-bee1-30b5fe2b0690", - "properties": { - "visible": true - } - }, - { - "id": "cbd9e3bc-6a49-432a-a906-b1674c1de24c", - "properties": { - "visible": true - } - }, - { - "id": "03262f07-8a15-416d-a3f5-e2bf561c78f9", - "properties": { - "visible": true - } - }, - { - "id": "e5c93b87-83fc-45b6-b4d5-bf1e3f523075", - "properties": { - "visible": true - } - }, - { - "id": "72260ac5-3d51-49d7-bb31-f794dd129f1c", - "properties": { - "visible": true - } - }, - { - "id": "d856bde1-1b42-4935-9bee-c37e886c9ecf", - "properties": { - "visible": true - } - }, - { - "id": "62380509-bedf-4134-95c3-77ff377a4a6a", - "properties": { - "visible": true - } - }, - { - "id": "f4ed5ac9-b386-49a6-a0a0-6f3341ce9021", - "properties": { - "visible": true - } - }, - { - "id": "528d2bd2-87fe-4a49-954a-c93a03256929", - "properties": { - "visible": true - } - }, - { - "id": "ff8951f1-61a7-416b-9223-fac4bb6dac50", - "properties": { - "visible": true - } - }, - { - "id": "95c2b011-d782-4042-8a07-6aa4a5765c2e", - "properties": { - "visible": true - } - }, - { - "id": "dbe5837b-0624-4a05-91f3-67b5bd9b812a", - "properties": { - "visible": true - } - }, - { - "id": "13f198ed-82ab-4e51-8144-bfaa5bf77fd5", - "properties": { - "visible": true - } - }, - { - "id": "025312eb-12b6-47e6-9750-0fb31ddc2111", - "properties": { - "visible": true - } - }, - { - "id": "24292d58-db66-4ef3-8f4f-005d7b719433", - "properties": { - "visible": true - } - }, - { - "id": "22e5b5c4-821c-413a-a5b1-ab866d9a03bb", - "properties": { - "visible": true - } - }, - { - "id": "2fde0aac-df89-403d-998e-854b949c7b57", - "properties": { - "visible": true - } - }, - { - "id": "8b576876-5c16-4178-805e-24984c24fac3", - "properties": { - "visible": true - } - }, - { - "id": "415b7d2a-b362-4f1e-b83a-927802328ecb", - "properties": { - "visible": true - } - }, - { - "id": "8ef24fc2-ab25-4f22-9d9f-61902b49dc01", - "properties": { - "visible": true - } - }, - { - "id": "2299b09e-9f8e-4b79-a55c-a7edacde2c85", - "properties": { - "visible": true - } - }, - { - "id": "bf506538-f438-425c-be85-5aa2f9b075b8", - "properties": { - "visible": true - } - }, - { - "id": "2b501dc6-799d-4675-9144-fac77c50c57c", - "properties": { - "visible": true - } - }, - { - "id": "c0446da1-e069-417e-bd5a-34edcd028edc", - "properties": { - "visible": false - } - } - ] - } -}` + { + "annotations": { + "annotation": [ + { + "name": "version", + "value": "8" + }, + { + "name": "versionTag", + "value": "Published on May 13, 2015 at 8:48pm (MST)" + } + ] + }, + "attributes": { + "attribute-key": [ + { + "id": "3b05c943-d81a-436f-b242-8b519e7a6f30", + "properties": { + "visible": true + } + }, + { + "id": "d794c7ee-2a4b-4da4-bba7-e8b973d50c4b", + "properties": { + "visible": true + } + }, + { + "id": "a0259458-517c-480f-9f04-9b54b1b2af1f", + "properties": { + "visible": true + } + }, + { + "id": "9415f39d-c396-4458-9019-fc076c847964", + "properties": { + "visible": true + } + }, + { + "id": "0a2e49a9-8989-42fb-97da-cc66334f828b", + "properties": { + "visible": true + } + }, + { + "id": "27f5f14a-ea97-4feb-b22a-6ff754a31212", + "properties": { + "visible": true + } + }, + { + "id": "6f810508-4615-4fd0-9e87-80f9c94f9ad8", + "properties": { + "visible": true + } + }, + { + "id": "3451b1b2-7365-455c-8bb1-0b464d4d3ba1", + "properties": { + "visible": true + } + }, + { + "id": "a82ec957-8c26-41ea-8af6-6dd75c384801", + "properties": { + "visible": true + } + }, + { + "id": "736c5496-9a6e-4a82-aa00-456725796432", + "properties": { + "visible": true + } + }, + { + "id": "2d428b3c-9d3b-4ec1-bf98-e00673599d60", + "properties": { + "visible": true + } + }, + { + "id": "68566ebb-811d-4337-aba9-a8a8baf90e4b", + "properties": { + "visible": true + } + }, + { + "id": "ca88bab1-a1ea-40cc-8f96-96d1e9f1217d", + "properties": { + "visible": true + } + }, + { + "id": "c63a12c8-542d-47f3-bee1-30b5fe2b0690", + "properties": { + "visible": true + } + }, + { + "id": "cbd9e3bc-6a49-432a-a906-b1674c1de24c", + "properties": { + "visible": true + } + }, + { + "id": "03262f07-8a15-416d-a3f5-e2bf561c78f9", + "properties": { + "visible": true + } + }, + { + "id": "e5c93b87-83fc-45b6-b4d5-bf1e3f523075", + "properties": { + "visible": true + } + }, + { + "id": "72260ac5-3d51-49d7-bb31-f794dd129f1c", + "properties": { + "visible": true + } + }, + { + "id": "d856bde1-1b42-4935-9bee-c37e886c9ecf", + "properties": { + "visible": true + } + }, + { + "id": "62380509-bedf-4134-95c3-77ff377a4a6a", + "properties": { + "visible": true + } + }, + { + "id": "f4ed5ac9-b386-49a6-a0a0-6f3341ce9021", + "properties": { + "visible": true + } + }, + { + "id": "528d2bd2-87fe-4a49-954a-c93a03256929", + "properties": { + "visible": true + } + }, + { + "id": "ff8951f1-61a7-416b-9223-fac4bb6dac50", + "properties": { + "visible": true + } + }, + { + "id": "95c2b011-d782-4042-8a07-6aa4a5765c2e", + "properties": { + "visible": true + } + }, + { + "id": "dbe5837b-0624-4a05-91f3-67b5bd9b812a", + "properties": { + "visible": true + } + }, + { + "id": "13f198ed-82ab-4e51-8144-bfaa5bf77fd5", + "properties": { + "visible": true + } + }, + { + "id": "025312eb-12b6-47e6-9750-0fb31ddc2111", + "properties": { + "visible": true + } + }, + { + "id": "24292d58-db66-4ef3-8f4f-005d7b719433", + "properties": { + "visible": true + } + }, + { + "id": "22e5b5c4-821c-413a-a5b1-ab866d9a03bb", + "properties": { + "visible": true + } + }, + { + "id": "2fde0aac-df89-403d-998e-854b949c7b57", + "properties": { + "visible": true + } + }, + { + "id": "8b576876-5c16-4178-805e-24984c24fac3", + "properties": { + "visible": true + } + }, + { + "id": "415b7d2a-b362-4f1e-b83a-927802328ecb", + "properties": { + "visible": true + } + }, + { + "id": "8ef24fc2-ab25-4f22-9d9f-61902b49dc01", + "properties": { + "visible": true + } + }, + { + "id": "2299b09e-9f8e-4b79-a55c-a7edacde2c85", + "properties": { + "visible": true + } + }, + { + "id": "bf506538-f438-425c-be85-5aa2f9b075b8", + "properties": { + "visible": true + } + }, + { + "id": "2b501dc6-799d-4675-9144-fac77c50c57c", + "properties": { + "visible": true + } + }, + { + "id": "c0446da1-e069-417e-bd5a-34edcd028edc", + "properties": { + "visible": false + } + } + ] + } + }` + +var superComplexTestCollections = Collections{ + EntitySets: EntitySets{}, + Arrays: []string{"$.annotations.annotation", "$.attributes.attribute-key"}, +} func TestSuperComplexSame(t *testing.T) { - patch, e := CreatePatch([]byte(superComplexBase), []byte(superComplexBase)) + patch, e := CreatePatch([]byte(superComplexBase), []byte(superComplexBase), superComplexTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 0, len(patch), "they should be equal") } func TestSuperComplexBoolReplace(t *testing.T) { - patch, e := CreatePatch([]byte(superComplexBase), []byte(superComplexA)) + patch, e := CreatePatch([]byte(superComplexBase), []byte(superComplexA), superComplexTestCollections, PatchStrategyExactMatch) assert.NoError(t, e) assert.Equal(t, 1, len(patch), "they should be equal") change := patch[0] assert.Equal(t, "replace", change.Operation, "they should be equal") assert.Equal(t, "/attributes/attribute-key/36/properties/visible", change.Path, "they should be equal") - assert.Equal(t, false, change.Value, "they should be equal") + assert.Equal(t, change.Value, false, "they should be equal") }