Skip to content

Commit e11ce6b

Browse files
authored
mapstr: fix M.Clone behaviour for arrays of M (#262)
The current implementation does not clone in branches of slices in a mapstr.M, resulting in cases where the clone will share values with the original and allowing unexpected mutation of the original. This change fixes that by adding slices type handling. Note that this will not fix cases of unexpected sharing where the node value type is not a mapstr.M or map[string]any, so structs that are included with pointer fields will still show this effect.
1 parent 05eff47 commit e11ce6b

File tree

2 files changed

+39
-8
lines changed

2 files changed

+39
-8
lines changed

mapstr/mapstr.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,44 @@ func (m M) CopyFieldsTo(to M, key string) error {
154154
}
155155

156156
// Clone returns a copy of the M. It recursively makes copies of inner
157-
// maps.
157+
// maps. Nested arrays and non-map types are not cloned.
158158
func (m M) Clone() M {
159159
result := make(M, len(m))
160+
cloneMap(result, m)
161+
return result
162+
}
160163

161-
for k := range m {
162-
if innerMap, ok := tryToMapStr(m[k]); ok {
163-
result[k] = innerMap.Clone()
164-
} else {
165-
result[k] = m[k]
164+
func cloneMap(dst, src M) {
165+
for k, v := range src {
166+
switch v := v.(type) {
167+
case M:
168+
d := make(M, len(v))
169+
dst[k] = d
170+
cloneMap(d, v)
171+
case map[string]interface{}:
172+
d := make(map[string]interface{}, len(v))
173+
dst[k] = d
174+
cloneMap(d, v)
175+
case []M:
176+
a := make([]M, 0, len(v))
177+
for _, m := range v {
178+
d := make(M, len(m))
179+
cloneMap(d, m)
180+
a = append(a, d)
181+
}
182+
dst[k] = a
183+
case []map[string]interface{}:
184+
a := make([]map[string]interface{}, 0, len(v))
185+
for _, m := range v {
186+
d := make(map[string]interface{}, len(m))
187+
cloneMap(d, m)
188+
a = append(a, d)
189+
}
190+
dst[k] = a
191+
default:
192+
dst[k] = v
166193
}
167194
}
168-
169-
return result
170195
}
171196

172197
// HasKey returns true if the key exist. If an error occurs then false is

mapstr/mapstr_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ func TestClone(t *testing.T) {
348348
"c31": 1,
349349
"c32": 2,
350350
},
351+
"c4": []M{{"c41": 1}},
351352
}
352353

353354
// Clone the original mapstr and then increment every value in it. Ensures the test will fail if
@@ -364,6 +365,7 @@ func TestClone(t *testing.T) {
364365
"c31": 1,
365366
"c32": 2,
366367
},
368+
"c4": []M{{"c41": 1}},
367369
},
368370
cloned,
369371
)
@@ -377,6 +379,10 @@ func incrementMapstrValues(m M) {
377379
m[k] = v + 1
378380
case M:
379381
incrementMapstrValues(m[k].(M))
382+
case []M:
383+
for _, c := range v {
384+
incrementMapstrValues(c)
385+
}
380386
}
381387

382388
}

0 commit comments

Comments
 (0)