Skip to content

Instantly share code, notes, and snippets.

@jmptrader
Forked from soroushjp/deepcopy.go
Created October 3, 2021 20:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmptrader/7026a7976dfcbcfeeb2d4b84fe1ffa5d to your computer and use it in GitHub Desktop.
Save jmptrader/7026a7976dfcbcfeeb2d4b84fe1ffa5d 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.
// 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(&copy)
if err != nil {
return nil, err
}
return copy, nil
}
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