Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 35 additions & 22 deletions jsonpatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,36 +384,49 @@ func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation,
return patch, nil
}

// https://github.com/mattbaird/jsonpatch/pull/4
// compareArray generates remove and add operations for `av` and `bv`.
func compareArray(av, bv []interface{}, p string) []Operation {
retval := []Operation{}
// var err error
for i, v := range av {
found := false
for _, v2 := range bv {
if reflect.DeepEqual(v, v2) {
found = true
break
}
}
if !found {
retval = append([]Operation{NewPatch("remove", makePath(p, i), nil)}, retval...)
}

// 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([]Operation, len(retval))
for i := 0; i < len(retval); i++ {
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 interface{}) {
retval = append(retval, NewPatch("add", makePath(p, i), value))
})

for i, v := range bv {
found := false
for _, v2 := range av {
return retval
}

// 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{})) {
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.
continue
}
if reflect.DeepEqual(v, v2) {
found = true
// Mark this index as found since it matches exactly.
foundIndexes[i] = struct{}{}
reverseFoundIndexes[i2] = struct{}{}
break
}
}

newElementButEqualToAnExistingOne := found && i >= len(av)
if !found || newElementButEqualToAnExistingOne {
retval = append([]Operation{NewPatch("add", makePath(p, i), v)}, retval...)
if _, ok := foundIndexes[i]; !ok {
applyOp(i, v)
}
}

return retval
}
68 changes: 60 additions & 8 deletions jsonpatch_subarray_test.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,77 @@
package jsonpatch

import (
"log"
"encoding/json"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var subArray1_current = `{"created":1739943104628936621,"updated":0,"index":"test","path":"test","data":{"subFields":[{"one":"one","two":"two","three":3}]}}`
var subArray1_target = `{"created":1739943104628936621,"updated":1739942662496282458,"index":"test","path":"test","data":{"subFields":[{"one":"one","two":"two","three":3},{"one":"one","two":"two","three":3}]}}`
var subArray1_current = `{"created":1739943104628936621,"updated":0,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3}]}}`
var subArray1_target = `{"created":1739943104628936621,"updated":1739942662496282458,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3}]}}`

var subArray2_current = `{"created":1739943104628936621,"updated":1739942662496282458,"index":"test","path":"test","data":{"subFields":[{"one":"one","two":"two","three":3},{"one":"one","two":"two","three":3}]}}`
var subArray2_target = `{"created":1739943104628936621,"updated":1739942662496282459,"index":"test","path":"test","data":{"subFields":[{"one":"one","two":"two","three":3},{"one":"one","two":"two","three":3},{"one":"one","two":"two","three":3}]}}`
var subArray2_current = `{"created":1739943104628936621,"updated":1739942662496282458,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3}]}}`
var subArray2_target = `{"created":1739943104628936621,"updated":1739942662496282459,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3},{"one":"P","two":"two","three":3}]}}`

var subArray3_current = `{"created":1739943104628936621,"updated":1739942662496282459,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3},{"one":"P","two":"two","three":3}]}}`
var subArray3_target = `{"created":1739943104628936621,"updated":1739942662496282460,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3},{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3}]}}`

var subArray4_current = `{"created":1739943104628936621,"updated":1739942662496282460,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3},{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3}]}}`
var subArray4_target = `{"created":1739943104628936621,"updated":1739942662496282461,"index":"test","path":"test","data":{"subFields":[{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3},{"one":"P","two":"two","three":3},{"one":"B","two":"two","three":3},{"one":"P","two":"two","three":3}]}}`

var subArray5_current = `{"created":1739954651885800613,"updated":1739954651890302236,"index":"game","path":"","data":{"id":"18258f8986f08d85","deck":1739954651885767932,"started":1739954651885767932,"ended":0,"burn":false,"burning":0,"overriden":0,"overrider":"","voided":0,"voider":"","edited":0,"editor":"","status":"ongoing","cards":[{"suit":1,"value":10,"owner":"P","show":false},{"suit":1,"value":10,"owner":"B","show":false},{"suit":1,"value":10,"owner":"P","show":false},{"suit":1,"value":10,"owner":"B","show":false}],"tally":{"player":0,"banker":0},"previousCards":null,"result":{"status":"","playerPair":false,"bankerPair":false,"natural8":false,"natural9":false,"lucky6_2":false,"lucky6_3":false},"fakeCards":false}}`
var subArray5__target = `{"created":1739954651885800613,"updated":1739954651891192258,"index":"game","path":"","data":{"id":"18258f8986f08d85","deck":1739954651885767932,"started":1739954651885767932,"ended":0,"burn":false,"burning":0,"overriden":0,"overrider":"","voided":0,"voider":"","edited":0,"editor":"","status":"ongoing","cards":[{"suit":1,"value":10,"owner":"P","show":true},{"suit":1,"value":10,"owner":"B","show":true},{"suit":1,"value":10,"owner":"P","show":true},{"suit":1,"value":10,"owner":"B","show":true},{"suit":1,"value":10,"owner":"P","show":false}],"tally":{"player":0,"banker":0},"previousCards":null,"result":{"status":"","playerPair":false,"bankerPair":false,"natural8":false,"natural9":false,"lucky6_2":false,"lucky6_3":false},"fakeCards":false}}`

type Card struct {
Suit int `json:"suit"`
Value int `json:"value"`
Owner string `json:"owner"`
Show bool `json:"show"`
}

type GameData struct {
Cards []Card `json:"cards"`
}

type Game struct {
Data GameData `json:"data"`
}

func TestSubfieldArray(t *testing.T) {
patch, e := CreatePatch([]byte(subArray1_current), []byte(subArray1_target))
assert.NoError(t, e)
assert.Equal(t, len(patch), 2, "the patch should update 2 fields")
assert.Equal(t, 2, len(patch), "the patch should update 2 fields")

patch, e = CreatePatch([]byte(subArray2_current), []byte(subArray2_target))
assert.NoError(t, e)
log.Println("OP", patch)
assert.Equal(t, len(patch), 2, "the patch should update 2 fields")
assert.Equal(t, 2, len(patch), "the patch should update 2 fields")

patch, e = CreatePatch([]byte(subArray3_current), []byte(subArray3_target))
assert.NoError(t, e)
assert.Equal(t, 2, len(patch), "the patch should update 2 fields")

patch, e = CreatePatch([]byte(subArray4_current), []byte(subArray4_target))
assert.NoError(t, e)
assert.Equal(t, 2, len(patch), "the patch should update 2 fields")

patch, e = CreatePatch([]byte(subArray5_current), []byte(subArray5__target))
assert.NoError(t, e)
// log.Println("OP", patch)
assert.Equal(t, 8, len(patch), "the patch should update 2 fields")

_patch, err := json.Marshal(patch)
require.NoError(t, err)
obj, err := DecodePatch([]byte(_patch))
require.NoError(t, err)
patched, err := obj.Apply([]byte(subArray5_current))
require.NoError(t, err)

var _target = Game{}
json.Unmarshal([]byte(subArray5__target), &_target)
var _patched = Game{}
json.Unmarshal([]byte(patched), &_patched)

require.True(t, reflect.DeepEqual(_target, _patched))
}