fox/mapstruct/map2struct.go

108 lines
2.2 KiB
Go

package mapstruct
import (
"errors"
"fmt"
"reflect"
"strings"
)
// ToStruct converts a map[string]string to a struct.
func ToStruct(m map[string]string, out any, opts ...Option) error {
options := defaultOptions()
for _, opt := range opts {
opt(options)
}
if out == nil {
return errors.New("output is nil")
}
v := reflect.ValueOf(out)
if v.Kind() != reflect.Ptr || v.IsNil() {
return errors.New("output must be a non-nil pointer")
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return errors.New("output must be a pointer to struct")
}
return fillStructFromMap(m, v, options)
}
// fillStructFromMap performs the actual map-to-struct conversion.
func fillStructFromMap(m map[string]string, v reflect.Value, opts *Options) error {
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
fieldValue := v.Field(i)
if !fieldValue.CanSet() {
continue
}
name, skip := getFieldName(field, opts.TagName)
if skip {
continue
}
value, ok := m[name]
if !ok {
continue
}
if err := setField(m, fieldValue, field, value, opts); err != nil {
return fmt.Errorf("field %s: %w", name, err)
}
}
return nil
}
func setField(m map[string]string, fieldValue reflect.Value, field reflect.StructField, value string, opts *Options) error {
// Handle pointers
if fieldValue.Kind() == reflect.Ptr {
if fieldValue.IsNil() {
fieldValue.Set(reflect.New(field.Type.Elem()))
}
return setField(m, fieldValue.Elem(), field, value, opts)
}
// Apply hooks or basic conversion
result, err := applyMapToStructHook(value, field, opts)
if err != nil {
return err
}
// Handle nested structs
if fieldValue.Kind() == reflect.Struct {
nestedMap := make(map[string]string)
name, _ := getFieldName(field, opts.TagName)
prefix := name + "."
for k, v := range m {
if strings.HasPrefix(k, prefix) {
nestedMap[strings.TrimPrefix(k, prefix)] = v
}
}
if len(nestedMap) > 0 {
return fillStructFromMap(nestedMap, fieldValue, opts)
}
return nil
}
// Set the value
if result != nil {
rv := reflect.ValueOf(result)
if rv.Type().ConvertibleTo(fieldValue.Type()) {
fieldValue.Set(rv.Convert(fieldValue.Type()))
return nil
}
}
return fmt.Errorf("cannot convert %q to %v", value, fieldValue.Type())
}