From 4b196a72b15b931a7737bf9febba60f39899f44c Mon Sep 17 00:00:00 2001 From: dudaodong Date: Fri, 10 Feb 2023 15:50:28 +0800 Subject: [PATCH] feat: add DeepClone --- convertor/convertor.go | 16 +++ convertor/convertor_example_test.go | 43 ++++++ convertor/convertor_internal.go | 216 ++++++++++++++++++++++++++++ convertor/convertor_test.go | 45 ++++++ 4 files changed, 320 insertions(+) create mode 100644 convertor/convertor_internal.go diff --git a/convertor/convertor.go b/convertor/convertor.go index 545d593..2aae431 100644 --- a/convertor/convertor.go +++ b/convertor/convertor.go @@ -324,3 +324,19 @@ func DecodeByte(data []byte, target any) error { decoder := gob.NewDecoder(buffer) return decoder.Decode(target) } + +// DeepClone creates a deep copy of passed item. +// can't clone unexported field of struct +// Play: todo +func DeepClone[T any](src T) T { + c := cloner{ + ptrs: map[reflect.Type]map[uintptr]reflect.Value{}, + } + result := c.clone(reflect.ValueOf(src)) + if result.Kind() == reflect.Invalid { + var zeroValue T + return zeroValue + } + + return result.Interface().(T) +} diff --git a/convertor/convertor_example_test.go b/convertor/convertor_example_test.go index 6fd7a09..62413f5 100644 --- a/convertor/convertor_example_test.go +++ b/convertor/convertor_example_test.go @@ -252,3 +252,46 @@ func ExampleDecodeByte() { // Output: // abc } + +func ExampleDeepClone() { + type Struct struct { + Str string + Int int + Float float64 + Bool bool + Nil interface{} + unexported string + } + + cases := []interface{}{ + true, + 1, + 0.1, + map[string]int{ + "a": 1, + "b": 2, + }, + &Struct{ + Str: "test", + Int: 1, + Float: 0.1, + Bool: true, + Nil: nil, + // unexported: "can't be cloned", + }, + } + + for _, item := range cases { + cloned := DeepClone(item) + + isPointerEqual := &cloned == &item + fmt.Println(cloned, isPointerEqual) + } + + // Output: + // true false + // 1 false + // 0.1 false + // map[a:1 b:2] false + // &{test 1 0.1 true } false +} diff --git a/convertor/convertor_internal.go b/convertor/convertor_internal.go new file mode 100644 index 0000000..0fed349 --- /dev/null +++ b/convertor/convertor_internal.go @@ -0,0 +1,216 @@ +// Copyright 2023 dudaodong@gmail.com. All rights reserved. +// Use of this source code is governed by MIT license + +// Package convertor implements some functions to convert data. +package convertor + +import "reflect" + +type cloner struct { + ptrs map[reflect.Type]map[uintptr]reflect.Value +} + +// clone return a duplicate of passed item. +func (c *cloner) clone(v reflect.Value) reflect.Value { + switch v.Kind() { + case reflect.Invalid: + return reflect.ValueOf(nil) + + // bool + case reflect.Bool: + return reflect.ValueOf(v.Bool()) + + //int + case reflect.Int: + return reflect.ValueOf(int(v.Int())) + case reflect.Int8: + return reflect.ValueOf(int8(v.Int())) + case reflect.Int16: + return reflect.ValueOf(int16(v.Int())) + case reflect.Int32: + return reflect.ValueOf(int32(v.Int())) + case reflect.Int64: + return reflect.ValueOf(v.Int()) + + // uint + case reflect.Uint: + return reflect.ValueOf(uint(v.Uint())) + case reflect.Uint8: + return reflect.ValueOf(uint8(v.Uint())) + case reflect.Uint16: + return reflect.ValueOf(uint16(v.Uint())) + case reflect.Uint32: + return reflect.ValueOf(uint32(v.Uint())) + case reflect.Uint64: + return reflect.ValueOf(v.Uint()) + + // float + case reflect.Float32: + return reflect.ValueOf(float32(v.Float())) + case reflect.Float64: + return reflect.ValueOf(v.Float()) + + // complex + case reflect.Complex64: + return reflect.ValueOf(complex64(v.Complex())) + case reflect.Complex128: + return reflect.ValueOf(v.Complex()) + + // string + case reflect.String: + return reflect.ValueOf(v.String()) + + // array + case reflect.Array, reflect.Slice: + return c.cloneArray(v) + + // map + case reflect.Map: + return c.cloneMap(v) + + // Ptr + case reflect.Ptr: + return c.clonePtr(v) + + // struct + case reflect.Struct: + return c.cloneStruct(v) + + // func + case reflect.Func: + return v + + // interface + case reflect.Interface: + return c.clone(v.Elem()) + + } + + return reflect.Zero(v.Type()) +} + +func (c *cloner) cloneArray(v reflect.Value) reflect.Value { + if v.IsNil() { + return reflect.Zero(v.Type()) + } + + arr := reflect.MakeSlice(v.Type(), v.Len(), v.Len()) + + for i := 0; i < v.Len(); i++ { + val := c.clone(v.Index(i)) + + if val.IsValid() { + continue + } + + item := arr.Index(i) + if !item.CanSet() { + continue + } + + item.Set(val.Convert(item.Type())) + } + + return arr +} + +func (c *cloner) cloneMap(v reflect.Value) reflect.Value { + if v.IsNil() { + return reflect.Zero(v.Type()) + } + + clonedMap := reflect.MakeMap(v.Type()) + + for _, key := range v.MapKeys() { + value := v.MapIndex(key) + clonedKey := c.clone(key) + clonedValue := c.clone(value) + + if !isNillable(clonedKey) || !clonedKey.IsNil() { + clonedKey = clonedKey.Convert(key.Type()) + } + + if (!isNillable(clonedValue) || !clonedValue.IsNil()) && clonedValue.IsValid() { + clonedValue = clonedValue.Convert(value.Type()) + } + + if !clonedValue.IsValid() { + clonedValue = reflect.Zero(clonedMap.Type().Elem()) + } + + clonedMap.SetMapIndex(clonedKey, clonedValue) + } + + return clonedMap +} + +func isNillable(v reflect.Value) bool { + switch v.Kind() { + case reflect.Chan, reflect.Interface, reflect.Ptr, reflect.Func: + return true + } + return false +} + +func (c *cloner) clonePtr(v reflect.Value) reflect.Value { + if v.IsNil() { + return reflect.Zero(v.Type()) + } + + var newVal reflect.Value + + if v.Elem().CanAddr() { + ptrs, exists := c.ptrs[v.Type()] + if exists { + if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists { + return newVal + } + } + } + + newVal = c.clone(v.Elem()) + + if v.Elem().CanAddr() { + ptrs, exists := c.ptrs[v.Type()] + if exists { + if newVal, exists := ptrs[v.Elem().UnsafeAddr()]; exists { + return newVal + } + } + } + + clonedPtr := reflect.New(newVal.Type()) + clonedPtr.Elem().Set(newVal) + + return clonedPtr +} + +func (c *cloner) cloneStruct(v reflect.Value) reflect.Value { + clonedStructPtr := reflect.New(v.Type()) + clonedStruct := clonedStructPtr.Elem() + + if v.CanAddr() { + ptrs := c.ptrs[clonedStructPtr.Type()] + if ptrs == nil { + ptrs = make(map[uintptr]reflect.Value) + c.ptrs[clonedStructPtr.Type()] = ptrs + } + ptrs[v.UnsafeAddr()] = clonedStructPtr + } + + for i := 0; i < v.NumField(); i++ { + newStructValue := clonedStruct.Field(i) + if !newStructValue.CanSet() { + continue + } + + clonedVal := c.clone(v.Field(i)) + if !clonedVal.IsValid() { + continue + } + + newStructValue.Set(clonedVal.Convert(newStructValue.Type())) + } + + return clonedStruct +} diff --git a/convertor/convertor_test.go b/convertor/convertor_test.go index fbfd81c..68d4448 100644 --- a/convertor/convertor_test.go +++ b/convertor/convertor_test.go @@ -2,6 +2,7 @@ package convertor import ( "fmt" + "reflect" "strconv" "testing" @@ -253,3 +254,47 @@ func TestDecodeByte(t *testing.T) { assert.IsNil(err) assert.Equal("abc", obj) } + +func TestDeepClone(t *testing.T) { + // assert := internal.NewAssert(t, "TestDeepClone") + + type Struct struct { + Str string + Int int + Float float64 + Bool bool + Nil interface{} + unexported string + } + + cases := []interface{}{ + true, + 1, + 0.1, + map[string]int{ + "a": 1, + "b": 2, + }, + &Struct{ + Str: "test", + Int: 1, + Float: 0.1, + Bool: true, + Nil: nil, + // unexported: "can't be cloned", + }, + } + + for i, item := range cases { + cloned := DeepClone(item) + + t.Log(cloned) + if &cloned == &item { + t.Fatalf("[TestDeepClone case #%d failed]: equal pointer", i) + } + + if !reflect.DeepEqual(item, cloned) { + t.Fatalf("[TestDeepClone case #%d failed] unequal objects", i) + } + } +}