108 lines
2.2 KiB
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())
|
||
|
}
|