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()) }