Skip to content

Commit b90dcc6

Browse files
authored
Merge pull request #148 from dwertent/json-deepcopy
feat: Add DeepCopy method to JSONObject
2 parents fa03f8f + ded69df commit b90dcc6

File tree

4 files changed

+237
-0
lines changed

4 files changed

+237
-0
lines changed

pkg/fftypes/jsonobject.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,7 @@ func (jd JSONObject) Hash(jsonDesc string) (*Bytes32, error) {
233233
var b32 Bytes32 = sha256.Sum256(b)
234234
return &b32, nil
235235
}
236+
237+
func (jd JSONObject) DeepCopy() JSONObject {
238+
return deepCopyMap(jd)
239+
}

pkg/fftypes/jsonobject_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,50 @@ func TestJSONNestedSafeGet(t *testing.T) {
254254
)
255255

256256
}
257+
258+
func TestDeepCopyBasic(t *testing.T) {
259+
original := JSONObject{
260+
"key1": "value1",
261+
"key2": 123,
262+
"key3": true,
263+
}
264+
265+
copy := original.DeepCopy()
266+
267+
assert.Equal(t, original, copy)
268+
assert.NotSame(t, original, copy)
269+
}
270+
271+
func TestDeepCopyNested(t *testing.T) {
272+
original := JSONObject{
273+
"key1": JSONObject{
274+
"nestedKey1": "nestedValue1",
275+
"nestedKey2": 456,
276+
},
277+
"key2": []interface{}{"elem1", "elem2"},
278+
}
279+
280+
copy := original.DeepCopy()
281+
282+
assert.Equal(t, original, copy)
283+
assert.NotSame(t, original, copy)
284+
assert.NotSame(t, original["key1"], copy["key1"])
285+
assert.NotSame(t, original["key2"], copy["key2"])
286+
}
287+
288+
func TestDeepCopyEmpty(t *testing.T) {
289+
original := JSONObject{}
290+
291+
copy := original.DeepCopy()
292+
293+
assert.Equal(t, original, copy)
294+
assert.NotSame(t, original, copy)
295+
}
296+
297+
func TestDeepCopyNil(t *testing.T) {
298+
var original JSONObject = nil
299+
300+
copy := original.DeepCopy()
301+
302+
assert.Nil(t, copy)
303+
}

pkg/fftypes/jsonobjectutils.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright © 2024 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package fftypes
18+
19+
func deepCopyMap(original map[string]interface{}) map[string]interface{} {
20+
if original == nil {
21+
return nil
22+
}
23+
co := make(map[string]interface{}, len(original))
24+
for key, value := range original {
25+
switch v := value.(type) {
26+
case map[string]interface{}:
27+
co[key] = deepCopyMap(v)
28+
case []interface{}:
29+
co[key] = deepCopySlice(v)
30+
default:
31+
co[key] = v
32+
}
33+
}
34+
return co
35+
}
36+
37+
func deepCopySlice(original []interface{}) []interface{} {
38+
if original == nil {
39+
return nil
40+
}
41+
co := make([]interface{}, len(original))
42+
for i, value := range original {
43+
switch v := value.(type) {
44+
case map[string]interface{}:
45+
co[i] = deepCopyMap(v)
46+
case []interface{}:
47+
co[i] = deepCopySlice(v)
48+
default:
49+
co[i] = v
50+
}
51+
}
52+
return co
53+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright © 2024 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package fftypes
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestDeepCopyMapNil(t *testing.T) {
26+
original := map[string]interface{}(nil)
27+
copy := deepCopyMap(original)
28+
assert.Nil(t, copy)
29+
}
30+
31+
func TestDeepCopyMapEmpty(t *testing.T) {
32+
original := map[string]interface{}{}
33+
copy := deepCopyMap(original)
34+
assert.NotNil(t, copy)
35+
assert.Empty(t, copy)
36+
}
37+
38+
func TestDeepCopyMapSimple(t *testing.T) {
39+
original := map[string]interface{}{
40+
"key1": "value1",
41+
"key2": 42,
42+
}
43+
copy := deepCopyMap(original)
44+
assert.Equal(t, original, copy)
45+
}
46+
47+
func TestDeepCopyMapNestedMap(t *testing.T) {
48+
original := map[string]interface{}{
49+
"key1": map[string]interface{}{
50+
"nestedKey1": "nestedValue1",
51+
},
52+
}
53+
copy := deepCopyMap(original)
54+
assert.Equal(t, original, copy)
55+
assert.NotSame(t, original["key1"], copy["key1"])
56+
}
57+
58+
func TestDeepCopyMapNestedSlice(t *testing.T) {
59+
original := map[string]interface{}{
60+
"key1": []interface{}{"value1", 42},
61+
}
62+
copy := deepCopyMap(original)
63+
assert.Equal(t, original, copy)
64+
assert.NotSame(t, original["key1"], copy["key1"])
65+
}
66+
67+
func TestDeepCopyMapMixed(t *testing.T) {
68+
original := map[string]interface{}{
69+
"key1": "value1",
70+
"key2": map[string]interface{}{
71+
"nestedKey1": "nestedValue1",
72+
},
73+
"key3": []interface{}{"value1", 42},
74+
}
75+
copy := deepCopyMap(original)
76+
assert.Equal(t, original, copy)
77+
assert.NotSame(t, original["key2"], copy["key2"])
78+
assert.NotSame(t, original["key3"], copy["key3"])
79+
}
80+
81+
func TestDeepCopySliceNil(t *testing.T) {
82+
original := []interface{}(nil)
83+
copy := deepCopySlice(original)
84+
assert.Nil(t, copy)
85+
}
86+
87+
func TestDeepCopySliceEmpty(t *testing.T) {
88+
original := []interface{}{}
89+
copy := deepCopySlice(original)
90+
assert.NotNil(t, copy)
91+
assert.Empty(t, copy)
92+
}
93+
94+
func TestDeepCopySliceSimple(t *testing.T) {
95+
original := []interface{}{"value1", 42}
96+
copy := deepCopySlice(original)
97+
assert.Equal(t, original, copy)
98+
}
99+
100+
func TestDeepCopySliceNestedMap(t *testing.T) {
101+
original := []interface{}{
102+
map[string]interface{}{
103+
"nestedKey1": "nestedValue1",
104+
},
105+
}
106+
copy := deepCopySlice(original)
107+
assert.Equal(t, original, copy)
108+
assert.NotSame(t, original[0], copy[0])
109+
}
110+
111+
func TestDeepCopySliceNestedSlice(t *testing.T) {
112+
original := []interface{}{
113+
[]interface{}{"value1", 42},
114+
}
115+
copy := deepCopySlice(original)
116+
assert.Equal(t, original, copy)
117+
assert.NotSame(t, original[0], copy[0])
118+
}
119+
120+
func TestDeepCopySliceMixed(t *testing.T) {
121+
original := []interface{}{
122+
"value1",
123+
42,
124+
map[string]interface{}{
125+
"nestedKey1": "nestedValue1",
126+
},
127+
[]interface{}{"value2", 43},
128+
}
129+
copy := deepCopySlice(original)
130+
assert.Equal(t, original, copy)
131+
assert.NotSame(t, original[2], copy[2])
132+
assert.NotSame(t, original[3], copy[3])
133+
}

0 commit comments

Comments
 (0)