map->struct
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package copier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -37,6 +38,7 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// fmt.Println("do deep copy src:", src.Kind().String(), "dst:", dst.Kind().String())
|
||||
switch src.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
// slice -> slice
|
||||
@@ -47,17 +49,26 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
|
||||
// map -> map
|
||||
return copyMap(dst, src, depth, opt)
|
||||
case reflect.Struct:
|
||||
// struct -> map
|
||||
return copyStruct2Map(dst, src, depth, opt)
|
||||
// map -> struct
|
||||
return copyMap2Struct(dst, src, depth, opt)
|
||||
case reflect.Pointer:
|
||||
elemType := dst.Type().Elem()
|
||||
newPtr := reflect.New(elemType)
|
||||
|
||||
if err := deepCopy(newPtr.Elem(), src, depth+1, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst.Set(newPtr)
|
||||
return nil
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
switch dst.Kind() {
|
||||
case reflect.Map:
|
||||
// map -> struct
|
||||
return copyMap2Struct(dst, src, depth, opt)
|
||||
// struct -> map
|
||||
return copyStruct2Map(dst, src, depth, opt)
|
||||
case reflect.Struct:
|
||||
// struct -> struct
|
||||
return copyStruct(dst, src, depth, opt)
|
||||
@@ -90,10 +101,14 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
|
||||
return deepCopy(dst, src, depth, opt)
|
||||
|
||||
case reflect.Interface:
|
||||
if src.Kind() != dst.Kind() {
|
||||
if src.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if src.Kind() != dst.Kind() {
|
||||
return set(dst, src)
|
||||
}
|
||||
|
||||
src = src.Elem()
|
||||
newDst := reflect.New(src.Type().Elem())
|
||||
|
||||
@@ -103,16 +118,97 @@ func deepCopy(dst, src reflect.Value, depth int, opt *options) error {
|
||||
|
||||
dst.Set(newDst)
|
||||
return nil
|
||||
case reflect.Chan:
|
||||
return ErrNotSupported
|
||||
default:
|
||||
return copyDefault(dst, src)
|
||||
return set(dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
func copyStruct2Map(dst, src reflect.Value, depth int, opt *options) error {
|
||||
for i, n := 0, src.NumField(); i < n; i++ {
|
||||
sf := src.Type().Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
if sf.Anonymous {
|
||||
switch sf.Type.Kind() {
|
||||
case reflect.Struct, reflect.Pointer:
|
||||
if err := deepCopy(dst, src.Field(i), depth+1, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
tag := parseTag(sf.Tag.Get(opt.tagName))
|
||||
if tag.Contains(tagIgnore) {
|
||||
continue
|
||||
}
|
||||
|
||||
name := toName(sf.Name, tag, opt)
|
||||
|
||||
sField := src.Field(i)
|
||||
|
||||
if opt.ignoreEmpty && sField.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
if sField.Kind() == reflect.Pointer || sField.Kind() == reflect.Struct {
|
||||
var newDst = reflect.ValueOf(make(map[string]any))
|
||||
sField = indirect(sField)
|
||||
|
||||
if err := deepCopy(newDst, sField, depth+1, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst.SetMapIndex(reflect.ValueOf(name), newDst)
|
||||
} else {
|
||||
dst.SetMapIndex(reflect.ValueOf(name), sField)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyMap2Struct(dst, src reflect.Value, depth int, opt *options) error {
|
||||
// 循环结构,然后从map取值
|
||||
typ := dst.Type()
|
||||
for i := 0; i < dst.NumField(); i++ {
|
||||
field := dst.Field(i)
|
||||
sf := typ.Field(i)
|
||||
|
||||
if !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldName := getFieldName(sf)
|
||||
if fieldName == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
mapValue := src.MapIndex(reflect.ValueOf(fieldName))
|
||||
if !mapValue.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
if mapValue.Kind() == reflect.Interface {
|
||||
mapValue = mapValue.Elem()
|
||||
}
|
||||
|
||||
if mapValue.Kind() == reflect.Map || mapValue.Kind() == reflect.Array || mapValue.Kind() == reflect.Slice {
|
||||
if err := deepCopy(field, mapValue, depth+1, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := set(field, mapValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -131,7 +227,14 @@ func copyMap(dst, src reflect.Value, depth int, opt *options) error {
|
||||
var copitedValue reflect.Value
|
||||
switch dstType.Elem().Kind() {
|
||||
case reflect.Interface:
|
||||
switch value.Kind() {
|
||||
case reflect.Interface:
|
||||
copitedValue = reflect.New(value.Elem().Type()).Elem()
|
||||
default:
|
||||
copitedValue = reflect.New(value.Type()).Elem()
|
||||
}
|
||||
|
||||
set(copitedValue, value)
|
||||
default:
|
||||
copitedValue = reflect.New(dstType.Elem()).Elem()
|
||||
}
|
||||
@@ -189,7 +292,7 @@ func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
|
||||
|
||||
name := toName(sf.Name, tag, opt)
|
||||
|
||||
dstValue := fieldByName(dst, name, opt) // fieldByName(dst, name, opt)
|
||||
dstValue := fieldByName(dst, name, opt)
|
||||
if !dstValue.IsValid() {
|
||||
continue
|
||||
}
|
||||
@@ -206,18 +309,116 @@ func copyStruct(dst, src reflect.Value, depth int, opt *options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDefault(dst, src reflect.Value) error {
|
||||
if src.Type().AssignableTo(dst.Type()) || src.Type().ConvertibleTo(dst.Type()) {
|
||||
func set(dst, src reflect.Value) error {
|
||||
if !src.IsValid() {
|
||||
return ErrInvalidCopyFrom
|
||||
}
|
||||
|
||||
if src.Kind() == reflect.Interface {
|
||||
src = src.Elem()
|
||||
}
|
||||
|
||||
if dst.Kind() == reflect.Pointer {
|
||||
if dst.IsNil() {
|
||||
if !dst.CanSet() {
|
||||
return ErrInvalidCopyDestination
|
||||
}
|
||||
dst.Set(reflect.New(dst.Type().Elem()))
|
||||
}
|
||||
|
||||
dst = dst.Elem()
|
||||
}
|
||||
|
||||
if src.Type().AssignableTo(dst.Type()) {
|
||||
dst.Set(src)
|
||||
return nil
|
||||
}
|
||||
|
||||
if src.Type().ConvertibleTo(dst.Type()) {
|
||||
dst.Set(src.Convert(dst.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
switch dst.Kind() {
|
||||
|
||||
if _, ok := dst.Interface().(time.Time); ok {
|
||||
switch v := src.Interface().(type) {
|
||||
case string:
|
||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
||||
dst.Set(reflect.ValueOf(t))
|
||||
}
|
||||
case int64:
|
||||
dst.Set(reflect.ValueOf(time.Unix(v, 0)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
switch dst.Kind() {
|
||||
case reflect.String:
|
||||
switch v := src.Interface().(type) {
|
||||
case string:
|
||||
dst.SetString(v)
|
||||
case []byte:
|
||||
dst.SetString(string(v))
|
||||
default:
|
||||
dst.SetString(fmt.Sprintf("%v", v))
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
switch v := src.Interface().(type) {
|
||||
case int:
|
||||
dst.SetInt(int64(v))
|
||||
case int8:
|
||||
dst.SetInt(int64(v))
|
||||
case int16:
|
||||
dst.SetInt(int64(v))
|
||||
case int32:
|
||||
dst.SetInt(int64(v))
|
||||
case int64:
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
switch v := src.Interface().(type) {
|
||||
case uint:
|
||||
dst.SetUint(uint64(v))
|
||||
case uint8:
|
||||
dst.SetUint(uint64(v))
|
||||
case uint16:
|
||||
dst.SetUint(uint64(v))
|
||||
case uint32:
|
||||
dst.SetUint(uint64(v))
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch v := src.Interface().(type) {
|
||||
case float32:
|
||||
dst.SetFloat(float64(v))
|
||||
case float64:
|
||||
dst.SetFloat(v)
|
||||
}
|
||||
case reflect.Bool:
|
||||
switch v := src.Interface().(type) {
|
||||
case bool:
|
||||
dst.SetBool(v)
|
||||
}
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
if tag := field.Tag.Get("json"); tag != "" {
|
||||
if commaIndex := strings.Index(tag, ","); commaIndex != -1 {
|
||||
name := tag[:commaIndex]
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
} else if tag != "" {
|
||||
return tag
|
||||
}
|
||||
}
|
||||
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func toName(name string, tag *tagOption, opt *options) string {
|
||||
if tag != nil && tag.Contains(tagToName) {
|
||||
return tag.toname
|
||||
|
||||
+181
-3
@@ -3,6 +3,7 @@ package copier
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/charlienet/go-misc/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -14,14 +15,14 @@ func TestCopyMap(t *testing.T) {
|
||||
Age int
|
||||
}
|
||||
|
||||
src := map[string]*person{
|
||||
"John": {
|
||||
src := map[string]any{
|
||||
"John": person{
|
||||
Name: "John",
|
||||
Age: 30,
|
||||
},
|
||||
}
|
||||
|
||||
dst := map[string]*person{}
|
||||
dst := map[string]person{}
|
||||
|
||||
assert.NoError(t, Copy(&dst, src))
|
||||
|
||||
@@ -30,6 +31,18 @@ func TestCopyMap(t *testing.T) {
|
||||
fmt.Println(json.Struct2JsonIndent(dst))
|
||||
}
|
||||
|
||||
func TestMapAnyCopy(t *testing.T) {
|
||||
src := map[string]any{
|
||||
"department": "Engineering",
|
||||
"level": 3,
|
||||
}
|
||||
|
||||
dst := map[string]any{}
|
||||
assert.NoError(t, Copy(&dst, src))
|
||||
|
||||
fmt.Println(src, dst)
|
||||
}
|
||||
|
||||
func TestDiffStructMapCopy(t *testing.T) {
|
||||
type person struct {
|
||||
Name string
|
||||
@@ -54,3 +67,168 @@ func TestDiffStructMapCopy(t *testing.T) {
|
||||
|
||||
fmt.Println(src, dst)
|
||||
}
|
||||
|
||||
func TestStructToMapCopy(t *testing.T) {
|
||||
type person struct {
|
||||
Name string
|
||||
Age int
|
||||
Address *Address
|
||||
}
|
||||
|
||||
src := person{
|
||||
Name: "John",
|
||||
Age: 30,
|
||||
Address: &Address{
|
||||
City: "Beijing",
|
||||
},
|
||||
}
|
||||
|
||||
dst := map[string]any{}
|
||||
assert.NoError(t, Copy(&dst, src, WithIgnoreEmpty()))
|
||||
fmt.Println(src, dst)
|
||||
|
||||
println(json.Struct2JsonIndent(dst))
|
||||
}
|
||||
|
||||
func TestAnonymousStructToMapCopy(t *testing.T) {
|
||||
type person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
type person2 struct {
|
||||
*person
|
||||
Address *Address
|
||||
}
|
||||
|
||||
src := person2{
|
||||
person: &person{
|
||||
Name: "John",
|
||||
Age: 30,
|
||||
},
|
||||
Address: &Address{
|
||||
City: "Beijing",
|
||||
},
|
||||
}
|
||||
|
||||
dst := map[string]any{}
|
||||
assert.NoError(t, Copy(&dst, src, WithIgnoreEmpty()))
|
||||
|
||||
println(dst, json.Struct2JsonIndent(dst))
|
||||
|
||||
}
|
||||
|
||||
func TestMapToStructCopy(t *testing.T) {
|
||||
type person struct {
|
||||
Name string
|
||||
Age int
|
||||
Address *Address
|
||||
}
|
||||
|
||||
src := map[string]any{
|
||||
"Name": "John",
|
||||
"Age": 30,
|
||||
"Address": map[string]any{
|
||||
"City": "Beijing",
|
||||
},
|
||||
}
|
||||
|
||||
dst := person{}
|
||||
assert.NoError(t, Copy(&dst, src, WithIgnoreEmpty()))
|
||||
fmt.Println(dst, json.Struct2JsonIndent(dst))
|
||||
}
|
||||
|
||||
func TestMixMapToStruct(t *testing.T) {
|
||||
type Address struct {
|
||||
Street string `json:"street"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Height float64 `json:"height"`
|
||||
IsStudent bool `json:"is_student"`
|
||||
Birthday time.Time `json:"birthday"`
|
||||
Tags []string `json:"tags"`
|
||||
Scores []int `json:"scores"`
|
||||
Address Address `json:"address"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
PtrField *string `json:"ptr_field"`
|
||||
}
|
||||
|
||||
src := map[string]any{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"height": 165.5,
|
||||
"is_student": true,
|
||||
"birthday": "1998-05-15T06:00:00Z",
|
||||
"tags": []any{"developer", "golang", "backend"},
|
||||
"scores": []any{95, 88, 92},
|
||||
"address": map[string]any{
|
||||
"street": "123 Main St",
|
||||
"city": "Beijing",
|
||||
"country": "China",
|
||||
},
|
||||
"metadata": map[string]any{
|
||||
"department": "Engineering",
|
||||
"level": 3,
|
||||
"projects": []any{"projectA", "projectB"},
|
||||
},
|
||||
"ptr_field": "pointer value",
|
||||
}
|
||||
|
||||
var dst Person
|
||||
assert.NoError(t, Copy(&dst, src, WithIgnoreEmpty()))
|
||||
fmt.Println(json.Struct2JsonIndent(dst))
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkMapCopy(b *testing.B) {
|
||||
type Address struct {
|
||||
Street string `json:"street"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Height float64 `json:"height"`
|
||||
IsStudent bool `json:"is_student"`
|
||||
Birthday time.Time `json:"birthday"`
|
||||
Tags []string `json:"tags"`
|
||||
Scores []int `json:"scores"`
|
||||
Address Address `json:"address"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
PtrField *string `json:"ptr_field"`
|
||||
}
|
||||
|
||||
src := map[string]any{
|
||||
"name": "Alice",
|
||||
"age": 25,
|
||||
"height": 165.5,
|
||||
"is_student": true,
|
||||
"birthday": "1998-05-15T00:00:00Z",
|
||||
"tags": []any{"developer", "golang", "backend"},
|
||||
"scores": []any{95, 88, 92},
|
||||
"address": map[string]any{
|
||||
"street": "123 Main St",
|
||||
"city": "Beijing",
|
||||
"country": "China",
|
||||
},
|
||||
"metadata": map[string]any{
|
||||
"department": "Engineering",
|
||||
"level": 3,
|
||||
"projects": []any{"projectA", "projectB"},
|
||||
},
|
||||
"ptr_field": "pointer value",
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
var dst Person
|
||||
assert.NoError(b, Copy(&dst, src, WithIgnoreEmpty()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,3 +58,11 @@ func TestPtrSliceCopy(t *testing.T) {
|
||||
assert.NotSame(t, src[i], dst[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAndSlice(t *testing.T) {
|
||||
src := []any{"developer", "golang", "backend"}
|
||||
dst := []string{}
|
||||
|
||||
assert.NoError(t, Copy(&dst, src))
|
||||
assert.Equal(t, []string{"developer", "golang", "backend"}, dst)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user