Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Sort remove operations alphabetically and numerically by path index #5

Closed
wants to merge 5 commits into from
Closed
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
50 changes: 47 additions & 3 deletions jsonpatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
)

Expand Down Expand Up @@ -45,6 +47,25 @@ 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 }

type ByPathIndex []Operation

func (a ByPathIndex) Len() int { return len(a) }
func (a ByPathIndex) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByPathIndex) Less(i, j int) bool {
sp1 := strings.Split(a[i].Path, "/")
sp2 := strings.Split(a[j].Path, "/")
idx1, err := strconv.Atoi(sp1[len(sp1)-1])
if err != nil {
panic(fmt.Sprintf("expected int. got: %T", sp1[len(sp1)-1]))
}
idx2, err := strconv.Atoi(sp2[len(sp2)-1])
if err != nil {
panic(fmt.Sprintf("expected int. got: %T", sp2[len(sp2)-1]))
}

return idx1 < idx2
}

func NewPatch(operation, path string, value interface{}) Operation {
return Operation{Operation: operation, Path: path, Value: value}
}
Expand Down Expand Up @@ -83,12 +104,12 @@ func matchesValue(av, bv interface{}) bool {
return true
}
case float64:
bt, ok := bv.(float64)
bt, ok := bv.(float64)
if ok && bt == at {
return true
}
case bool:
bt, ok := bv.(bool)
bt, ok := bv.(bool)
if ok && bt == at {
return true
}
Expand Down Expand Up @@ -170,6 +191,7 @@ func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operat
return nil, err
}
}

// Now add all deleted values as nil
for key := range a {
_, found := b[key]
Expand All @@ -179,6 +201,7 @@ func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operat
patch = append(patch, NewPatch("remove", p, nil))
}
}

return patch, nil
}

Expand All @@ -203,7 +226,28 @@ func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation,
case []interface{}:
bt := bv.([]interface{})
if isSimpleArray(at) && isSimpleArray(bt) {
patch = append(patch, compareEditDistance(at, bt, p)...)
ptc := compareEditDistance(at, bt, p)

// Gather removals and non-removals
var removals []Operation
var filtered []Operation
for _, v := range ptc {
if v.Operation == "remove" {
removals = append(removals, v)
} else {
filtered = append(filtered, v)
}
}

// Sort alphabetically by path
sort.Sort(sort.Reverse(ByPath(removals)))

// Sort numerically by path index
sort.Sort(sort.Reverse(ByPathIndex(removals)))

// Add back removals in sorted order
ptc = append(filtered, removals...)
patch = append(patch, ptc...)
} else {
n := min(len(at), len(bt))
for i := len(at) - 1; i >= n; i-- {
Expand Down
95 changes: 79 additions & 16 deletions jsonpatch_array_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
package jsonpatch

import (
"github.com/stretchr/testify/assert"
"sort"
"testing"

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

var arraySrc = `
{
"spec": {
"loadBalancerSourceRanges": [
"192.101.0.0/16",
"192.0.0.0/24"
]
}
"spec": {
"loadBalancerSourceRanges": [
"192.101.0.0/16",
"192.0.0.0/24"
]
}
}
`

var arrayDst = `
{
"spec": {
"loadBalancerSourceRanges": [
"192.101.0.0/24"
]
}
"spec": {
"loadBalancerSourceRanges": [
"192.101.0.0/24"
]
}
}
`

Expand Down Expand Up @@ -55,14 +56,76 @@ func TestArrayAlmostSame(t *testing.T) {
patch, e := CreatePatch([]byte(src), []byte(to))
assert.NoError(t, e)
assert.Equal(t, 2, len(patch), "they should be equal")
sort.Sort(ByPath(patch))

change := patch[0]
assert.Equal(t, change.Operation, "add", "they should be equal")
assert.Equal(t, change.Path, "/Lines/10", "they should be equal")
assert.Equal(t, float64(11), change.Value, "they should be equal")
change = patch[1]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/Lines/0", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")
}

func TestArrayReplacement(t *testing.T) {
src := `{"letters":["A","B","C","D","E","F","G","H","I","J","K"]}`
to := `{"letters":["L","M","N"]}`
patch, e := CreatePatch([]byte(src), []byte(to))
assert.NoError(t, e)
assert.Equal(t, 11, len(patch), "they should be equal")

change := patch[0]
assert.Equal(t, change.Operation, "replace", "they should be equal")
assert.Equal(t, change.Path, "/letters/0", "they should be equal")
assert.Equal(t, "L", change.Value, "they should be equal")

change = patch[1]
assert.Equal(t, change.Operation, "add", "they should be equal")
assert.Equal(t, change.Path, "/Lines/10", "they should be equal")
assert.Equal(t, float64(11), change.Value, "they should be equal")
assert.Equal(t, change.Operation, "replace", "they should be equal")
assert.Equal(t, change.Path, "/letters/1", "they should be equal")
assert.Equal(t, "M", change.Value, "they should be equal")

change = patch[2]
assert.Equal(t, change.Operation, "replace", "they should be equal")
assert.Equal(t, change.Path, "/letters/2", "they should be equal")
assert.Equal(t, "N", change.Value, "they should be equal")

change = patch[3]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/10", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[4]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/9", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[5]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/8", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[6]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/7", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[7]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/6", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[8]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/5", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[9]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/4", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")

change = patch[10]
assert.Equal(t, "remove", change.Operation, "they should be equal")
assert.Equal(t, "/letters/3", change.Path, "they should be equal")
assert.Equal(t, nil, change.Value, "they should be equal")
}