Add package for deep copying

This commit is contained in:
Tobie Morgan Hitchcock 2016-09-12 16:47:07 +01:00
parent 6f9372cb66
commit c3ecaed152
2 changed files with 320 additions and 0 deletions

87
util/deep/deep.go Normal file
View file

@ -0,0 +1,87 @@
// Copyright © 2016 Abcum Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deep
import (
"reflect"
)
// Copy returns a deep copy of the source object.
func Copy(src interface{}) interface{} {
if src == nil {
return nil
}
prime := reflect.ValueOf(src)
clone := reflect.New(prime.Type()).Elem()
copy(prime, clone)
return clone.Interface()
}
func copy(prime, clone reflect.Value) {
switch prime.Kind() {
case reflect.Ptr:
value := prime.Elem()
if !value.IsValid() {
return
}
clone.Set(reflect.New(value.Type()))
copy(value, clone.Elem())
case reflect.Interface:
if prime.IsNil() {
return
}
value := prime.Elem()
alike := reflect.New(value.Type()).Elem()
copy(value, alike)
clone.Set(alike)
case reflect.Struct:
for i := 0; i < prime.NumField(); i++ {
if prime.Type().Field(i).PkgPath != "" {
continue
}
copy(prime.Field(i), clone.Field(i))
}
case reflect.Slice:
clone.Set(reflect.MakeSlice(prime.Type(), prime.Len(), prime.Cap()))
for i := 0; i < prime.Len(); i++ {
copy(prime.Index(i), clone.Index(i))
}
case reflect.Map:
clone.Set(reflect.MakeMap(prime.Type()))
for _, key := range prime.MapKeys() {
value := prime.MapIndex(key)
alike := reflect.New(value.Type()).Elem()
copy(value, alike)
clone.SetMapIndex(key, alike)
}
default:
clone.Set(prime)
}
}

233
util/deep/deep_test.go Normal file
View file

@ -0,0 +1,233 @@
package deep
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
type Mini struct {
embedded bool
}
type Test struct {
PublicNumber int
PublicString string
PublicStruct interface{}
PublicPointr interface{}
PublicSlice []interface{}
PublicMap map[string]interface{}
privateNumber int
privateString string
privateStruct interface{}
privatePointr interface{}
privateSlice []interface{}
privateMap map[string]interface{}
}
func TestMain(t *testing.T) {
Convey("Can copy nil", t, func() {
var item interface{}
item = nil
done := Copy(item)
So(item, ShouldResemble, done)
})
}
func TestSimple(t *testing.T) {
Convey("Can copy bool", t, func() {
item := true
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy int64", t, func() {
item := int64(1)
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy float64", t, func() {
item := float64(1)
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy string", t, func() {
item := "string"
done := Copy(item)
So(item, ShouldResemble, done)
})
}
func TestSlices(t *testing.T) {
Convey("Can copy slice of bools", t, func() {
item := []bool{
true,
false,
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy slice of int64s", t, func() {
item := []int64{
1,
2,
3,
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy slice of float64s", t, func() {
item := []float64{
1,
2,
3,
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy slice of strings", t, func() {
item := []string{
"str",
"str",
"str",
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy slice of interfaces", t, func() {
item := []interface{}{
nil,
int64(1),
float64(2),
"str",
}
done := Copy(item)
So(item, ShouldResemble, done)
})
}
func TestObjects(t *testing.T) {
Convey("Can copy map of bools", t, func() {
item := map[string]bool{
"a": true,
"b": false,
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy map of int64s", t, func() {
item := map[string]int64{
"a": 1,
"b": 2,
"c": 3,
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy map of float64s", t, func() {
item := map[string]float64{
"a": 1,
"b": 2,
"c": 3,
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy map of strings", t, func() {
item := map[string]string{
"a": "str",
"b": "str",
"c": "str",
}
done := Copy(item)
So(item, ShouldResemble, done)
})
Convey("Can copy map of interfaces", t, func() {
item := map[interface{}]interface{}{
1: nil,
"b": int64(1),
true: float64(2),
"d": "str",
}
done := Copy(item)
So(item, ShouldResemble, done)
})
}
func TestStructs(t *testing.T) {
full := Test{
PublicNumber: 1,
PublicString: "str",
PublicStruct: Mini{},
PublicPointr: &Mini{},
PublicSlice: []interface{}{nil, 1, "str", true},
PublicMap: map[string]interface{}{
"a": 1,
"b": "str",
"c": true,
},
privateNumber: 1,
privateString: "str",
privateStruct: Mini{},
privatePointr: &Mini{},
privateSlice: []interface{}{nil, 1, "str", true},
privateMap: map[string]interface{}{
"a": 1,
"b": "str",
"c": true,
},
}
show := Test{
PublicNumber: full.PublicNumber,
PublicString: full.PublicString,
PublicStruct: full.PublicStruct,
PublicPointr: full.PublicPointr,
PublicSlice: full.PublicSlice,
PublicMap: full.PublicMap,
}
Convey("Can copy struct", t, func() {
item := full
done := Copy(item)
So(done, ShouldResemble, show)
})
Convey("Can copy pointer", t, func() {
item := &full
done := Copy(item)
So(done, ShouldResemble, &show)
})
Convey("Can copy slice of structs", t, func() {
item := []interface{}{full, full}
done := Copy(item)
So(done, ShouldResemble, []interface{}{show, show})
})
Convey("Can copy slice of pointers", t, func() {
item := []interface{}{&full, &full}
done := Copy(item)
So(done, ShouldResemble, []interface{}{&show, &show})
})
}