From 48519aba2b1d5f0b91bb2bb9b08b656e37a98807 Mon Sep 17 00:00:00 2001 From: dudaodong Date: Fri, 23 Feb 2024 10:29:46 +0800 Subject: [PATCH] feat: add MapToStruct --- convertor/convertor.go | 55 ++++++------------------- convertor/convertor_internal.go | 72 ++++++++++++++++++++++++++++++++- convertor/convertor_test.go | 37 +++++++++++++++++ docs/convertor.md | 57 +++++++++++++++++++++++++- docs/convertor_zh-CN.md | 54 +++++++++++++++++++++++++ 5 files changed, 231 insertions(+), 44 deletions(-) diff --git a/convertor/convertor.go b/convertor/convertor.go index 0522886..2332074 100644 --- a/convertor/convertor.go +++ b/convertor/convertor.go @@ -294,48 +294,6 @@ func DeepClone(src interface{}) interface{} { return result.Interface() } -// // CopyProperties copies each field from the source into the destination. It recursively copies struct pointers and interfaces that contain struct pointers. -// func CopyProperties(dst, src interface{}) (err error) { -// defer func() { -// if e := recover(); e != nil { -// err = errors.New(fmt.Sprintf("%v", e)) -// } -// }() - -// dstType, dstValue := reflect.TypeOf(dst), reflect.ValueOf(dst) -// srcType, srcValue := reflect.TypeOf(src), reflect.ValueOf(src) - -// if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct { -// return errors.New("CopyProperties: param dst should be struct pointer") -// } - -// if srcType.Kind() == reflect.Ptr { -// srcType, srcValue = srcType.Elem(), srcValue.Elem() -// } -// if srcType.Kind() != reflect.Struct { -// return errors.New("CopyProperties: param src should be a struct or struct pointer") -// } - -// dstType, dstValue = dstType.Elem(), dstValue.Elem() - -// propertyNums := dstType.NumField() - -// for i := 0; i < propertyNums; i++ { -// property := dstType.Field(i) -// propertyValue := srcValue.FieldByName(property.Name) - -// if !propertyValue.IsValid() || property.Type != propertyValue.Type() { -// continue -// } - -// if dstValue.Field(i).CanSet() { -// dstValue.Field(i).Set(propertyValue) -// } -// } - -// return nil -// } - // CopyProperties copies each field from the source into the destination. It recursively copies struct pointers and interfaces that contain struct pointers. // use json.Marshal/Unmarshal, so json tag should be set for fields of dst and src struct. func CopyProperties(dst, src interface{}) error { @@ -404,3 +362,16 @@ func GbkToUtf8(bs []byte) ([]byte, error) { b, err := io.ReadAll(r) return b, err } + +// MapToStruct converts map to struct +// Play: todo +func MapToStruct(m map[string]interface{}, structObj interface{}) error { + for k, v := range m { + err := setStructField(structObj, k, v) + if err != nil { + return err + } + } + + return nil +} diff --git a/convertor/convertor_internal.go b/convertor/convertor_internal.go index 0fed349..796c9d3 100644 --- a/convertor/convertor_internal.go +++ b/convertor/convertor_internal.go @@ -4,7 +4,10 @@ // Package convertor implements some functions to convert data. package convertor -import "reflect" +import ( + "fmt" + "reflect" +) type cloner struct { ptrs map[reflect.Type]map[uintptr]reflect.Value @@ -214,3 +217,70 @@ func (c *cloner) cloneStruct(v reflect.Value) reflect.Value { return clonedStruct } + +func setStructField(structObj interface{}, fieldName string, fieldValue interface{}) error { + structVal := reflect.ValueOf(structObj).Elem() + + fName := getFieldNameByJsonTag(structObj, fieldName) + if fName == "" { + return fmt.Errorf("Struct field json tag don't match map key : %s in obj", fieldName) + } + + fieldVal := structVal.FieldByName(fName) + + if !fieldVal.IsValid() { + return fmt.Errorf("No such field: %s in obj", fieldName) + } + + if !fieldVal.CanSet() { + return fmt.Errorf("Cannot set %s field value", fieldName) + } + + val := reflect.ValueOf(fieldValue) + + if fieldVal.Type() != val.Type() { + + if val.CanConvert(fieldVal.Type()) { + fieldVal.Set(val.Convert(fieldVal.Type())) + return nil + } + + if m, ok := fieldValue.(map[string]interface{}); ok { + + if fieldVal.Kind() == reflect.Struct { + return MapToStruct(m, fieldVal.Addr().Interface()) + } + + if fieldVal.Kind() == reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct { + if fieldVal.IsNil() { + fieldVal.Set(reflect.New(fieldVal.Type().Elem())) + } + + return MapToStruct(m, fieldVal.Interface()) + } + + } + + return fmt.Errorf("Map value type don't match struct field type") + } + + fieldVal.Set(val) + + return nil +} + +func getFieldNameByJsonTag(structObj interface{}, jsonTag string) string { + s := reflect.TypeOf(structObj).Elem() + + for i := 0; i < s.NumField(); i++ { + field := s.Field(i) + tag := field.Tag + name := tag.Get("json") + + if name == jsonTag { + return field.Name + } + } + + return "" +} diff --git a/convertor/convertor_test.go b/convertor/convertor_test.go index 9306004..29e8b04 100644 --- a/convertor/convertor_test.go +++ b/convertor/convertor_test.go @@ -363,3 +363,40 @@ func TestGbkToUtf8(t *testing.T) { assert.Equal(true, utf8.Valid(utf8Data)) assert.Equal("hello", string(utf8Data)) } + +func TestMapToStruct(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestMapToStruct") + + type Address struct { + Street string `json:"street"` + Number int `json:"number"` + } + + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + Phone string `json:"phone"` + Addr *Address `json:"address"` + } + + m := map[string]interface{}{ + "name": "Nothin", + "age": 28, + "phone": "123456789", + "address": map[string]interface{}{ + "street": "test", + "number": 1, + }, + } + + var p Person + err := MapToStruct(m, &p) + assert.IsNil(err) + assert.Equal(m["name"], p.Name) + assert.Equal(m["age"], p.Age) + assert.Equal(m["phone"], p.Phone) + assert.Equal("test", p.Addr.Street) + assert.Equal(1, p.Addr.Number) +} diff --git a/docs/convertor.md b/docs/convertor.md index 767bf7d..830749d 100644 --- a/docs/convertor.md +++ b/docs/convertor.md @@ -33,6 +33,7 @@ import ( - [ToJson](#ToJson) - [ToString](#ToString) - [StructToMap](#StructToMap) +- [MapToStruct](#MapToStruct) - [EncodeByte](#EncodeByte) - [DecodeByte](#DecodeByte) - [DeepClone](#DeepClone) @@ -40,6 +41,7 @@ import ( - [ToInterface](#ToInterface) - [Utf8ToGbk](#Utf8ToGbk) - [GbkToUtf8](#GbkToUtf8) +- [GbkToUtf8](#GbkToUtf8)
@@ -356,7 +358,7 @@ func main() { ### StructToMap -

Convert struct to map, only convert exported field, struct field tag `json` should be set.

+

Converts struct to map, only convert exported field, struct field tag `json` should be set.

Signature: @@ -389,6 +391,59 @@ func main() { } ``` +### MapToStruct + +

Converts map to struct, only convert exported field, struct field tag `json` should be set.

+ +Signature: + +```go +func MapToStruct(m map[string]interface{}, structObj interface{}) error +``` + +Example: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/convertor" +) + +func main() { + type Address struct { + Street string `json:"street"` + Number int `json:"number"` + } + + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + Phone string `json:"phone"` + Addr *Address `json:"address"` + } + + m := map[string]interface{}{ + "name": "Nothin", + "age": 28, + "phone": "123456789", + "address": map[string]interface{}{ + "street": "test", + "number": 1, + }, + } + + var p Person + err := MapToStruct(m, &p) + if err != nil { + return + } + + fmt.Printf("p.Addr.Street: %s", p.Addr.Street) //test +} +``` + ### EncodeByte

Encode data to byte slice.

diff --git a/docs/convertor_zh-CN.md b/docs/convertor_zh-CN.md index db0c07f..0019598 100644 --- a/docs/convertor_zh-CN.md +++ b/docs/convertor_zh-CN.md @@ -33,6 +33,7 @@ import ( - [ToJson](#ToJson) - [ToString](#ToString) - [StructToMap](#StructToMap) +- [MapToStruct](#MapToStruct) - [EncodeByte](#EncodeByte) - [DecodeByte](#DecodeByte) - [DeepClone](#DeepClone) @@ -388,6 +389,59 @@ func main() { } ``` +### MapToStruct + +

将map转成struct,struct中导出字段需要设置json tag标记

+ +函数签名: + +```go +func MapToStruct(m map[string]interface{}, structObj interface{}) error +``` + +Example: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/convertor" +) + +func main() { + type Address struct { + Street string `json:"street"` + Number int `json:"number"` + } + + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + Phone string `json:"phone"` + Addr *Address `json:"address"` + } + + m := map[string]interface{}{ + "name": "Nothin", + "age": 28, + "phone": "123456789", + "address": map[string]interface{}{ + "street": "test", + "number": 1, + }, + } + + var p Person + err := MapToStruct(m, &p) + if err != nil { + return + } + + fmt.Printf("p.Addr.Street: %s", p.Addr.Street) //test +} +``` + ### EncodeByte

将data编码成字节切片