Add JSON Patch go package
This commit is contained in:
parent
955e736a4d
commit
1b83dace69
1 changed files with 238 additions and 0 deletions
238
util/diff/diff.go
Normal file
238
util/diff/diff.go
Normal file
|
@ -0,0 +1,238 @@
|
|||
// Copyright © 2016 Abcum Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
type Operation struct {
|
||||
Op string `cork:"op,omietmpty" json:"op,omietmpty" structs:"op,omitempty"`
|
||||
From string `cork:"from,omitempty" json:"from,omitempty" structs:"from,omitempty"`
|
||||
Path string `cork:"path,omitempty" json:"path,omitempty" structs:"path,omitempty"`
|
||||
Value interface{} `cork:"value,omitempty" json:"value,omitempty" structs:"value,omitempty"`
|
||||
Before interface{} `cork:"-" json:"-" structs:"-"`
|
||||
}
|
||||
|
||||
type Operations struct {
|
||||
Ops []*Operation
|
||||
}
|
||||
|
||||
func Diff(old, now map[string]interface{}) (ops *Operations) {
|
||||
|
||||
ops = &Operations{}
|
||||
|
||||
ops.diff(old, now, "")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (o *Operations) Patch(old map[string]interface{}) (now map[string]interface{}, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *Operations) Rebase(other *Operations) (ops *Operations, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *Operations) Out() (ops []map[string]interface{}) {
|
||||
|
||||
for _, v := range o.Ops {
|
||||
ops = append(ops, structs.Map(v))
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func route(path string, part interface{}) string {
|
||||
if path == "" {
|
||||
return fmt.Sprintf("/%v", part)
|
||||
} else {
|
||||
if strings.HasSuffix(path, "/") {
|
||||
return path + fmt.Sprintf("%v", part)
|
||||
} else {
|
||||
return path + fmt.Sprintf("/%v", part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operations) op(op, from, path string, before, after interface{}) {
|
||||
|
||||
o.Ops = append(o.Ops, &Operation{
|
||||
Op: op,
|
||||
From: from,
|
||||
Path: path,
|
||||
Value: after,
|
||||
Before: before,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (o *Operations) diff(old, now map[string]interface{}, path string) {
|
||||
|
||||
for key, after := range now {
|
||||
|
||||
p := route(path, key)
|
||||
|
||||
// Check if the value existed
|
||||
before, ok := old[key]
|
||||
|
||||
// Value did not previously exist
|
||||
if !ok {
|
||||
o.op("add", "", p, nil, after)
|
||||
continue
|
||||
}
|
||||
|
||||
// Data type is now completely different
|
||||
if reflect.TypeOf(after) != reflect.TypeOf(before) {
|
||||
o.op("replace", "", p, before, after)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check whether the values have changed
|
||||
o.vals(before, after, p)
|
||||
|
||||
}
|
||||
|
||||
for key, before := range old {
|
||||
|
||||
p := route(path, key)
|
||||
|
||||
// Check if the value exists
|
||||
after, ok := now[key]
|
||||
|
||||
// Value now no longer exists
|
||||
if !ok {
|
||||
o.op("remove", "", p, before, after)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var used []int
|
||||
|
||||
for i := len(o.Ops) - 1; i >= 0; i-- {
|
||||
if iv := o.Ops[i]; !isIn(i, used) && iv.Op == "add" {
|
||||
for j := len(o.Ops) - 1; j >= 0; j-- {
|
||||
if jv := o.Ops[j]; !isIn(j, used) && jv.Op == "remove" {
|
||||
if reflect.DeepEqual(iv.Value, jv.Before) {
|
||||
used = append(used, []int{i, j}...)
|
||||
o.op("move", jv.Path, iv.Path, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(used)))
|
||||
|
||||
for _, i := range used {
|
||||
o.Ops = append(o.Ops[:i], o.Ops[i+1:]...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func isIn(a int, list []int) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Operations) text(old, now string, path string) {
|
||||
|
||||
dmp := diffmatchpatch.New()
|
||||
|
||||
dif := dmp.DiffMain(old, now, false)
|
||||
|
||||
txt := dmp.DiffToDelta(dif)
|
||||
|
||||
o.op("change", "", path, old, txt)
|
||||
|
||||
}
|
||||
|
||||
func (o *Operations) vals(old, now interface{}, path string) {
|
||||
|
||||
if reflect.TypeOf(old) != reflect.TypeOf(now) {
|
||||
o.op("replace", "", path, old, now)
|
||||
return
|
||||
}
|
||||
|
||||
switch ov := old.(type) {
|
||||
default:
|
||||
if !reflect.DeepEqual(old, now) {
|
||||
o.op("replace", "", path, old, now)
|
||||
}
|
||||
case bool:
|
||||
if ov != now.(bool) {
|
||||
o.op("replace", "", path, old, now)
|
||||
}
|
||||
case int64:
|
||||
if ov != now.(int64) {
|
||||
o.op("replace", "", path, old, now)
|
||||
}
|
||||
case float64:
|
||||
if ov != now.(float64) {
|
||||
o.op("replace", "", path, old, now)
|
||||
}
|
||||
case string:
|
||||
if ov != now.(string) {
|
||||
o.text(ov, now.(string), path)
|
||||
}
|
||||
case nil:
|
||||
switch now.(type) {
|
||||
case nil:
|
||||
default:
|
||||
o.op("replace", "", path, old, now)
|
||||
}
|
||||
case map[string]interface{}:
|
||||
o.diff(ov, now.(map[string]interface{}), path)
|
||||
case []interface{}:
|
||||
o.arrs(ov, now.([]interface{}), path)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (o *Operations) arrs(old, now []interface{}, path string) {
|
||||
|
||||
var i int
|
||||
|
||||
for i = 0; i < len(old) && i < len(now); i++ {
|
||||
o.vals(old[i], now[i], route(path, i))
|
||||
}
|
||||
|
||||
for j := i; j < len(now); j++ {
|
||||
if j >= len(old) || !reflect.DeepEqual(now[j], old[j]) {
|
||||
o.op("add", "", route(path, j), nil, now[j])
|
||||
}
|
||||
}
|
||||
|
||||
for j := i; j < len(old); j++ {
|
||||
if j >= len(now) || !reflect.DeepEqual(old[j], now[j]) {
|
||||
o.op("remove", "", route(path, j), old[j], nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue