-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathmerge.go
More file actions
99 lines (90 loc) · 2.25 KB
/
Copy pathmerge.go
File metadata and controls
99 lines (90 loc) · 2.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package jsondiff
import (
"encoding/json"
)
// MergePatch returns a JSON Merge Patch (RFC 7386)
// of the differences between the JSON representations
// of the given values.
func MergePatch(src, tgt interface{}) ([]byte, error) {
opts := options{
marshal: json.Marshal,
unmarshal: json.Unmarshal,
}
si, _, err := marshalUnmarshal(src, opts)
if err != nil {
return nil, err
}
ti, _, err := marshalUnmarshal(tgt, opts)
if err != nil {
return nil, err
}
patch := mergePatch(si, ti)
if patch == nil {
return nil, nil
}
return json.Marshal(patch)
}
// MergePatchJSON compares the given JSON documents
// and returns the differences relative to the former
// as a JSON Merge Patch (RFC 7386)
func MergePatchJSON(src, tgt []byte) ([]byte, error) {
var si, ti interface{}
if err := json.Unmarshal(src, &si); err != nil {
return nil, err
}
if err := json.Unmarshal(tgt, &ti); err != nil {
return nil, err
}
patch := mergePatch(si, ti)
if patch == nil {
return nil, nil
}
return json.Marshal(patch)
}
func mergePatch(src, tgt interface{}) interface{} {
if src == nil || tgt == nil {
return tgt
}
// If the target is not of the same type as the source,
// or both are not objects, the patch replaces the entire
// source with the target.
// https://datatracker.ietf.org/doc/html/rfc7386#section-2
if jsonTypeSwitch(src) != jsonObject || jsonTypeSwitch(tgt) != jsonObject {
return tgt
}
sm := src.(map[string]interface{})
tm := tgt.(map[string]interface{})
cmpSet := make(map[string]uint8, max(len(sm), len(tm)))
for k := range sm {
cmpSet[k] |= 1 << 0
}
for k := range tm {
cmpSet[k] |= 1 << 1
}
keys := make([]string, 0, len(cmpSet))
for k := range cmpSet {
keys = append(keys, k)
}
sortStrings(keys)
patch := make(map[string]interface{}, len(sm))
for _, k := range keys {
v := cmpSet[k]
inOld := v&(1<<0) != 0
inNew := v&(1<<1) != 0
switch {
case inOld && inNew:
if !deepEqual(sm[k], tm[k]) {
patch[k] = mergePatch(sm[k], tm[k])
}
case inOld:
// Null values in the merge patch are given
// special meaning to indicate the removal
// of existing values in the target.
// https://datatracker.ietf.org/doc/html/rfc7386#section-1
patch[k] = nil
case inNew:
patch[k] = tm[k]
}
}
return patch
}