From 7ec2533b7ad94052ab08198a6343a51ff3ec168b Mon Sep 17 00:00:00 2001 From: dudaodong Date: Mon, 19 Feb 2024 13:50:06 +0800 Subject: [PATCH] feat: add MapToStruct --- maputil/map.go | 77 +++++++++++++++++++++++++++++++++++++++++++++ maputil/map_test.go | 39 +++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/maputil/map.go b/maputil/map.go index a445121..008f641 100644 --- a/maputil/map.go +++ b/maputil/map.go @@ -5,6 +5,7 @@ package maputil import ( + "fmt" "reflect" "github.com/duke-git/lancet/v2/slice" @@ -303,3 +304,79 @@ func HasKey[K comparable, V any](m map[K]V, key K) bool { _, haskey := m[key] return haskey } + +// 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 +} + +func setStructField(structObj any, fieldName string, fieldValue any) 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 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 any, 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/maputil/map_test.go b/maputil/map_test.go index 843d510..7753637 100644 --- a/maputil/map_test.go +++ b/maputil/map_test.go @@ -472,3 +472,42 @@ func TestHasKey(t *testing.T) { assert.Equal(true, HasKey(m, "a")) assert.Equal(false, HasKey(m, "c")) } + +func TestMapToStruct(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestMapToStruct") + + type ( + Person struct { + Name string `json:"name"` + Age int `json:"age"` + Phone string `json:"phone"` + Addr *Address `json:"address"` + } + + Address struct { + Street string `json:"street"` + Number int `json:"number"` + } + ) + + 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) +}