Skip to content

Commit ef6eaca

Browse files
committed
Support for map[string]interface{}
1 parent 05890ea commit ef6eaca

File tree

2 files changed

+41
-39
lines changed

2 files changed

+41
-39
lines changed

patch_test.go

+34-23
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ type B struct {
3636
}
3737

3838
type C struct {
39-
Str string `json:"str,omitempty"`
40-
Map map[string]string `json:"map"`
39+
Str string `json:"str,omitempty"`
40+
StrMap map[string]string `json:"strmap"`
41+
IntMap map[string]int `json:"intmap"`
42+
BoolMap map[string]bool `json:"boolmap"`
43+
StructMap map[string]B `json:"structmap"`
44+
PtrMap map[string]*B `json:"ptrmap"`
4145
}
4246

4347
type D struct {
@@ -154,17 +158,28 @@ var _ = Describe("JSONPatch", func() {
154158
})
155159
})
156160
Context("CreateJsonPatch_map", func() {
157-
It("map", func() {
161+
It("string map", func() {
158162
// add
159-
testPatch(C{Map: map[string]string{"key1": "value1"}}, C{})
163+
testPatch(C{StrMap: map[string]string{"key1": "value1"}}, C{})
160164
// remove
161-
testPatch(C{Map: map[string]string{}}, C{Map: map[string]string{"key1": "value1"}})
165+
testPatch(C{StrMap: map[string]string{}}, C{StrMap: map[string]string{"key1": "value1"}})
162166
// replace
163-
testPatch(C{Map: map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}}, C{Map: map[string]string{}})
164-
testPatch(C{Map: map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}}, C{Map: map[string]string{"key1": "value1"}})
165-
testPatch(C{Map: map[string]string{"key1": "value1"}}, C{Map: map[string]string{"key1": "value2"}})
167+
testPatch(C{StrMap: map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}}, C{StrMap: map[string]string{}})
168+
testPatch(C{StrMap: map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}}, C{StrMap: map[string]string{"key1": "value1"}})
169+
testPatch(C{StrMap: map[string]string{"key1": "value1"}}, C{StrMap: map[string]string{"key1": "value2"}})
166170
// no change
167-
testPatch(C{Map: map[string]string{"key1": "value1", "key2": "value2"}}, C{Map: map[string]string{"key1": "value1", "key2": "value2"}})
171+
testPatch(C{StrMap: map[string]string{"key1": "value1", "key2": "value2"}}, C{StrMap: map[string]string{"key1": "value1", "key2": "value2"}})
172+
})
173+
It("struct map", func() {
174+
// add
175+
testPatch(C{StructMap: map[string]B{"key1": {Str: "value1"}}}, C{})
176+
testPatch(C{StructMap: map[string]B{"key1": {Str: "value1"}, "key2": {Str: "value2"}}}, C{StructMap: map[string]B{"key1": {Str: "value1"}}})
177+
// remove
178+
testPatch(C{StructMap: map[string]B{"key1": {Str: "value1"}}}, C{StructMap: map[string]B{"key1": {Str: "value1"}, "key2": {Str: "value2"}}})
179+
// replace
180+
testPatch(C{StructMap: map[string]B{"key1": {Str: "value1", Bool: true}}}, C{StructMap: map[string]B{"key1": {Str: "old"}}})
181+
// no change
182+
testPatch(C{StructMap: map[string]B{"key1": {Str: "value1", Bool: true}, "key2": {Str: "value2"}}}, C{StructMap: map[string]B{"key1": {Str: "value1", Bool: true}, "key2": {Str: "value2"}}})
168183
})
169184
})
170185
Context("CreateJsonPatch_slice", func() {
@@ -234,14 +249,14 @@ var _ = Describe("JSONPatch", func() {
234249
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key2"}, {Str: "key3"}, {Str: "key4"}}}, D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key3"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
235250
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key3"}, {Str: "key2"}}}, D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key2"}, {Str: "key3"}, {Str: "key4"}}}, D{StructSliceWithKey: []C{{Str: "key2"}, {Str: "key3"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
236251
// replace
237-
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key", Map: map[string]string{"key": "value1"}}}}, D{StructSliceWithKey: []C{{Str: "key", Map: map[string]string{"key": "value2"}}}}, D{StructSliceWithKey: []C{{Str: "key", Map: map[string]string{"key": "value1"}}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
238-
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key", Map: map[string]string{"key1": "value"}}}}, D{StructSliceWithKey: []C{{Str: "key", Map: map[string]string{"key1": "value"}}}}, D{StructSliceWithKey: []C{{Str: "key", Map: map[string]string{"key1": "value"}}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
252+
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key", StrMap: map[string]string{"key": "value1"}}}}, D{StructSliceWithKey: []C{{Str: "key", StrMap: map[string]string{"key": "value2"}}}}, D{StructSliceWithKey: []C{{Str: "key", StrMap: map[string]string{"key": "value1"}}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
253+
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key", StrMap: map[string]string{"key1": "value"}}}}, D{StructSliceWithKey: []C{{Str: "key", StrMap: map[string]string{"key1": "value"}}}}, D{StructSliceWithKey: []C{{Str: "key", StrMap: map[string]string{"key1": "value"}}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
239254
// mixed
240255
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "new"}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key2"}, {Str: "key3"}, {Str: "key4"}}}, D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key3"}, {Str: "new"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
241256
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key3"}, {Str: "key2"}, {Str: "new"}}}, D{StructSliceWithKey: []C{{Str: "key1"}, {Str: "key2"}, {Str: "key3"}, {Str: "key4"}}}, D{StructSliceWithKey: []C{{Str: "key2"}, {Str: "key3"}, {Str: "new"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
242257
// no change
243-
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key3"}, {Str: "key2", Map: map[string]string{"key": "value"}}}}, D{StructSliceWithKey: []C{{Str: "key2", Map: map[string]string{"key": "value"}}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key2", Map: map[string]string{"key": "value"}}, {Str: "key3"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
244-
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key2", Map: map[string]string{"key": "value"}}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key2", Map: map[string]string{"key": "value"}}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key2", Map: map[string]string{"key": "value"}}, {Str: "key3"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
258+
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key3"}, {Str: "key2", StrMap: map[string]string{"key": "value"}}}}, D{StructSliceWithKey: []C{{Str: "key2", StrMap: map[string]string{"key": "value"}}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key2", StrMap: map[string]string{"key": "value"}}, {Str: "key3"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
259+
testPatchWithExpected(D{StructSliceWithKey: []C{{Str: "key2", StrMap: map[string]string{"key": "value"}}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key2", StrMap: map[string]string{"key": "value"}}, {Str: "key3"}}}, D{StructSliceWithKey: []C{{Str: "key2", StrMap: map[string]string{"key": "value"}}, {Str: "key3"}}}, jsonpatch.IgnoreSliceOrderWithPattern([]jsonpatch.IgnorePattern{{"/structsWithKey", "str"}}))
245260
})
246261
})
247262
Context("CreateJsonPatch_escape_pointer", func() {
@@ -302,7 +317,7 @@ var _ = Describe("JSONPatch", func() {
302317
ReplaceFunc: func(path jsonpatch.JSONPointer, modified, current interface{}) bool {
303318
if modifiedC, ok := modified.(C); ok {
304319
if currentC, ok := current.(C); ok {
305-
return len(modifiedC.Map) > len(currentC.Map)
320+
return len(modifiedC.StrMap) > len(currentC.StrMap)
306321
}
307322
}
308323

@@ -322,14 +337,14 @@ var _ = Describe("JSONPatch", func() {
322337
testPatchWithExpected(G{B: &B{Bool: true, Str: "str"}}, G{}, G{B: &B{Bool: true, Str: "str"}}, jsonpatch.WithPredicate(predicate))
323338
testPatchWithExpected(G{B: &B{Int: 7, Str: "str"}}, G{}, G{B: &B{Int: 7, Str: "str"}}, jsonpatch.WithPredicate(predicate))
324339
// don't add
325-
testPatchWithExpected(G{B: &B{Bool: false, Str: "str"}, C: C{Map: map[string]string{"key": "value"}}}, G{}, G{C: C{Map: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
326-
testPatchWithExpected(G{B: &B{Int: 0, Str: "str"}, C: C{Map: map[string]string{"key": "value"}}}, G{}, G{C: C{Map: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
340+
testPatchWithExpected(G{B: &B{Bool: false, Str: "str"}, C: C{StrMap: map[string]string{"key": "value"}}}, G{}, G{C: C{StrMap: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
341+
testPatchWithExpected(G{B: &B{Int: 0, Str: "str"}, C: C{StrMap: map[string]string{"key": "value"}}}, G{}, G{C: C{StrMap: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
327342
})
328343
It("predicate_replace", func() {
329344
// replace
330-
testPatchWithExpected(G{C: C{Str: "new", Map: map[string]string{"key": "value"}}}, G{C: C{Str: "old"}}, G{C: C{Str: "new", Map: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
345+
testPatchWithExpected(G{C: C{Str: "new", StrMap: map[string]string{"key": "value"}}}, G{C: C{Str: "old"}}, G{C: C{Str: "new", StrMap: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
331346
// don't replace
332-
testPatchWithExpected(G{C: C{Str: "new"}}, G{C: C{Str: "old", Map: map[string]string{"key": "value"}}}, G{C: C{Str: "old", Map: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
347+
testPatchWithExpected(G{C: C{Str: "new"}}, G{C: C{Str: "old", StrMap: map[string]string{"key": "value"}}}, G{C: C{Str: "old", StrMap: map[string]string{"key": "value"}}}, jsonpatch.WithPredicate(predicate))
333348
})
334349
It("predicate_remove", func() {
335350
// remove
@@ -398,10 +413,6 @@ var _ = Describe("JSONPatch", func() {
398413
_, err := jsonpatch.CreateJSONPatch(I{1}, I{"str"})
399414
Ω(err).Should(HaveOccurred())
400415
})
401-
It("invalid map (map[string]int)", func() {
402-
_, err := jsonpatch.CreateJSONPatch(I{map[string]int{"key": 2}}, I{map[string]int{"key": 3}})
403-
Ω(err).Should(HaveOccurred())
404-
})
405416
It("invalid map (map[int]string)", func() {
406417
_, err := jsonpatch.CreateJSONPatch(I{map[int]string{1: "value"}}, I{map[int]string{2: "value"}})
407418
Ω(err).Should(HaveOccurred())
@@ -428,7 +439,7 @@ var _ = Describe("JSONPatch", func() {
428439
Ω(err).ShouldNot(HaveOccurred())
429440
})
430441

431-
for i := 0; i < 100; i++ {
442+
for i := 0; i < 1000; i++ {
432443
It("fuzzy "+strconv.Itoa(i), func() {
433444
testPatch(modified, current)
434445
})

walker.go

+7-16
Original file line numberDiff line numberDiff line change
@@ -82,47 +82,38 @@ func (w *walker) processInterface(modified reflect.Value, current reflect.Value,
8282

8383
// processMap processes reflect.Map values
8484
func (w *walker) processMap(modified reflect.Value, current reflect.Value, pointer JSONPointer) error {
85-
// NOTE: currently only map[string]string are supported
85+
// NOTE: currently only map[string]interface{} are supported
8686
if len(modified.MapKeys()) > 0 && len(current.MapKeys()) == 0 {
8787
w.add(pointer, modified.Interface())
8888
} else {
8989
it := modified.MapRange()
9090
for it.Next() {
9191
key := it.Key()
9292
if key.Kind() != reflect.String {
93-
return fmt.Errorf("only map[string]string are supported but key was: %s", key.Kind())
93+
return fmt.Errorf("only strings are supported as map keys but was: %s at: %s", key.Kind(), pointer)
9494
}
9595

9696
val1 := it.Value()
9797
val2 := current.MapIndex(key)
98-
switch val2.Kind() {
99-
case reflect.Invalid:
100-
if val1.Kind() != reflect.String {
101-
return fmt.Errorf("only map[string]string are supported but value was: %s", val1.Kind())
102-
}
103-
w.add(pointer.Add(key.String()), val1.String())
104-
case reflect.String:
98+
if val2.Kind() == reflect.Invalid {
99+
w.add(pointer.Add(key.String()), val1.Interface())
100+
} else {
105101
if err := w.walk(val1, val2, pointer.Add(key.String())); err != nil {
106102
return err
107103
}
108-
default:
109-
return fmt.Errorf("only map[string]string are supported but value was: %s", val2.Kind())
110104
}
111105
}
112106
it = current.MapRange()
113107
for it.Next() {
114108
key := it.Key()
115109
if key.Kind() != reflect.String {
116-
return fmt.Errorf("only map[string]string are supported but key was: %s", key.Kind())
110+
return fmt.Errorf("only strings are supported as map keys but was: %s at: %s", key.Kind(), pointer)
117111
}
118112

119113
val1 := modified.MapIndex(key)
120114
val2 := it.Value()
121115
if val1.Kind() == reflect.Invalid {
122-
if val2.Kind() != reflect.String {
123-
return fmt.Errorf("only map[string]string are supported but value was: %s", val1.Kind())
124-
}
125-
w.remove(pointer.Add(key.String()), val2.String())
116+
w.remove(pointer.Add(key.String()), val2.Interface())
126117
}
127118
}
128119
}

0 commit comments

Comments
 (0)