136 lines
2.6 KiB
Go
136 lines
2.6 KiB
Go
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
|
|
}
|