fox/mapstruct/struct2map.go

136 lines
2.6 KiB
Go
Raw Normal View History

package mapstruct
import (
"errors"
"fmt"
"reflect"
)
// converts a struct to map[string]any.
func ToMap(input any, opts ...Option) (map[string]any, error) {
options := defaultOptions()
for _, opt := range opts {
opt(options)
}
v := reflect.ValueOf(input)
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil, errors.New("input is nil pointer")
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, errors.New("input is not a struct")
}
return convertStructToMap(v, options)
}
// convertStructToMap performs the actual struct-to-map conversion.
func convertStructToMap(v reflect.Value, opts *Options) (map[string]any, error) {
result := make(map[string]any)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
fieldValue := v.Field(i)
if !field.IsExported() {
continue
}
name, skip := getFieldName(field, opts.TagName)
if skip {
continue
}
value, err := convertField(fieldValue, field, opts)
if err != nil {
return nil, fmt.Errorf("field %s: %w", name, err)
}
if value != nil {
result[name] = value
}
}
return result, nil
}
// convertField converts a single struct field.
func convertField(v reflect.Value, field reflect.StructField, opts *Options) (any, error) {
// Apply hooks
if result, handled, err := applyStructToMapHook(v, field, opts); handled {
return result, err
}
// Handle pointers
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil, nil
}
v = v.Elem()
}
// Handle nested structs
if v.Kind() == reflect.Struct {
return convertStructToMap(v, opts)
}
// Handle slices/arrays
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
return convertSlice(v, opts)
}
// Handle maps
if v.Kind() == reflect.Map {
return convertMap(v, opts)
}
return v.Interface(), nil
}
// convertSlice converts a slice/array field.
func convertSlice(v reflect.Value, opts *Options) (any, error) {
sliceLen := v.Len()
result := make([]any, sliceLen)
for i := 0; i < sliceLen; i++ {
elem := v.Index(i)
val, err := convertField(elem, reflect.StructField{}, opts)
if err != nil {
return nil, err
}
result[i] = val
}
return result, nil
}
// convertMap converts a map field.
func convertMap(v reflect.Value, opts *Options) (any, error) {
result := make(map[string]any)
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
val := iter.Value()
keyStr, ok := key.Interface().(string)
if !ok {
return nil, fmt.Errorf("map key must be string, got %v", key.Kind())
}
value, err := convertField(val, reflect.StructField{}, opts)
if err != nil {
return nil, err
}
result[keyStr] = value
}
return result, nil
}