package reflectext import ( "encoding/json" "gogs.mikescher.com/BlackForestBytes/goext/langext" "reflect" "strings" ) type ConvertStructToMapOpt struct { KeepJsonMarshalTypes bool MaxDepth *int UseTagsAsKeys *string } func ConvertStructToMap(v any, opts ...ConvertStructToMapOpt) map[string]any { opt := ConvertStructToMapOpt{} if len(opts) > 0 { opt = opts[0] } res := reflectToMap(reflect.ValueOf(v), 1, opt) if v, ok := res.(map[string]any); ok { return v } else if langext.IsNil(res) { return nil } else { panic("not an object") } } func reflectToMap(fv reflect.Value, depth int, opt ConvertStructToMapOpt) any { if opt.MaxDepth != nil && depth > *opt.MaxDepth { return fv.Interface() } if fv.Kind() == reflect.Ptr { if fv.IsNil() { return nil } else { return reflectToMap(fv.Elem(), depth, opt) } } if fv.Kind() == reflect.Func { // skip return nil } if fv.Kind() == reflect.Array { arrlen := fv.Len() arr := make([]any, arrlen) for i := 0; i < arrlen; i++ { arr[i] = reflectToMap(fv.Index(i), depth+1, opt) } return arr } if fv.Kind() == reflect.Slice { arrlen := fv.Len() arr := make([]any, arrlen) for i := 0; i < arrlen; i++ { arr[i] = reflectToMap(fv.Index(i), depth+1, opt) } return arr } if fv.Kind() == reflect.Chan { // skip return nil } if fv.Kind() == reflect.Struct { if opt.KeepJsonMarshalTypes && fv.Type().Implements(reflect.TypeFor[json.Marshaler]()) { return fv.Interface() } res := make(map[string]any) for i := 0; i < fv.NumField(); i++ { if fv.Type().Field(i).IsExported() { k := fv.Type().Field(i).Name if opt.UseTagsAsKeys != nil { if tagval, ok := fv.Type().Field(i).Tag.Lookup(*opt.UseTagsAsKeys); ok { if strings.Contains(tagval, ",") { k = strings.TrimSpace(strings.Split(tagval, ",")[0]) } else { k = strings.TrimSpace(tagval) } } else { continue } } res[k] = reflectToMap(fv.Field(i), depth+1, opt) } } return res } return fv.Interface() }