Last active
September 22, 2024 16:10
-
-
Save soroushjp/0ec92102641ddfc3ad5515ca76405f4d to your computer and use it in GitHub Desktop.
Golang: deepcopy map[string]interface{}. Could be used for any other Go type with minor modifications.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package deepcopy provides a function for deep copying map[string]interface{} | |
// values. Inspired by the StackOverflow answer at: | |
// http://stackoverflow.com/a/28579297/1366283 | |
// | |
// Uses the golang.org/pkg/encoding/gob package to do this and therefore has the | |
// same caveats. | |
// See: https://blog.golang.org/gobs-of-data | |
// See: https://golang.org/pkg/encoding/gob/ | |
package deepcopy | |
import ( | |
"bytes" | |
"encoding/gob" | |
) | |
func init() { | |
gob.Register(map[string]interface{}{}) | |
} | |
// Map performs a deep copy of the given map m. | |
func Map(m map[string]interface{}) (map[string]interface{}, error) { | |
var buf bytes.Buffer | |
enc := gob.NewEncoder(&buf) | |
dec := gob.NewDecoder(&buf) | |
err := enc.Encode(m) | |
if err != nil { | |
return nil, err | |
} | |
var copy map[string]interface{} | |
err = dec.Decode(©) | |
if err != nil { | |
return nil, err | |
} | |
return copy, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package deepcopy | |
import ( | |
"testing" | |
"github.com/stretchr/testify/assert" | |
) | |
func TestMap(t *testing.T) { | |
testCases := []struct { | |
// original and expectedOriginal are the same value in each test case. We do | |
// this to avoid unintentionally asserting against a mutated | |
// expectedOriginal and having the test pass erroneously. We also do not | |
// want to rely on the deep copy function we are testing to ensure this does | |
// not happen. | |
original map[string]interface{} | |
transformer func(m map[string]interface{}) map[string]interface{} | |
expectedCopy map[string]interface{} | |
expectedOriginal map[string]interface{} | |
}{ | |
// reassignment of entire map, should be okay even without deepcopy. | |
{ | |
original: nil, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
return map[string]interface{}{} | |
}, | |
expectedCopy: map[string]interface{}{}, | |
expectedOriginal: nil, | |
}, | |
{ | |
original: map[string]interface{}{}, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
return nil | |
}, | |
expectedCopy: nil, | |
expectedOriginal: map[string]interface{}{}, | |
}, | |
// mutation of map | |
{ | |
original: map[string]interface{}{}, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
m["foo"] = "bar" | |
return m | |
}, | |
expectedCopy: map[string]interface{}{ | |
"foo": "bar", | |
}, | |
expectedOriginal: map[string]interface{}{}, | |
}, | |
{ | |
original: map[string]interface{}{ | |
"foo": "bar", | |
}, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
m["foo"] = "car" | |
return m | |
}, | |
expectedCopy: map[string]interface{}{ | |
"foo": "car", | |
}, | |
expectedOriginal: map[string]interface{}{ | |
"foo": "bar", | |
}, | |
}, | |
// mutation of nested maps | |
{ | |
original: map[string]interface{}{}, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
m["foo"] = map[string]interface{}{ | |
"biz": "baz", | |
} | |
return m | |
}, | |
expectedCopy: map[string]interface{}{ | |
"foo": map[string]interface{}{ | |
"biz": "baz", | |
}, | |
}, | |
expectedOriginal: map[string]interface{}{}, | |
}, | |
{ | |
original: map[string]interface{}{ | |
"foo": map[string]interface{}{ | |
"biz": "booz", | |
"gaz": "gooz", | |
}, | |
}, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
m["foo"] = map[string]interface{}{ | |
"biz": "baz", | |
} | |
return m | |
}, | |
expectedCopy: map[string]interface{}{ | |
"foo": map[string]interface{}{ | |
"biz": "baz", | |
}, | |
}, | |
expectedOriginal: map[string]interface{}{ | |
"foo": map[string]interface{}{ | |
"biz": "booz", | |
"gaz": "gooz", | |
}, | |
}, | |
}, | |
// mutation of slice values | |
{ | |
original: map[string]interface{}{ | |
"foo": []string{"biz", "baz"}, | |
}, | |
transformer: func(m map[string]interface{}) map[string]interface{} { | |
m["foo"].([]string)[0] = "hiz" | |
return m | |
}, | |
expectedCopy: map[string]interface{}{ | |
"foo": []string{"hiz", "baz"}, | |
}, | |
expectedOriginal: map[string]interface{}{ | |
"foo": []string{"biz", "baz"}, | |
}, | |
}, | |
} | |
for i, tc := range testCases { | |
copy, err := Map(tc.original) | |
assert.NoError(t, err) | |
assert.Exactly(t, tc.expectedCopy, tc.transformer(copy), "copy was not mutated. test case: %d", i) | |
assert.Exactly(t, tc.expectedOriginal, tc.original, "original was mutated. test case: %d", i) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cool 😁