Skip to content

Commit

Permalink
feat: add MapToStruct
Browse files Browse the repository at this point in the history
  • Loading branch information
duke-git committed Feb 19, 2024
1 parent 9fd0603 commit 7ec2533
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
77 changes: 77 additions & 0 deletions maputil/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package maputil

import (
"fmt"
"reflect"

"github.com/duke-git/lancet/v2/slice"
Expand Down Expand Up @@ -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 ""
}
39 changes: 39 additions & 0 deletions maputil/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 7ec2533

Please sign in to comment.