package helper import ( "encoding/json" "fmt" "reflect" "strings" ) func MapToStruct(source map[string]interface{}, target interface{}) error { jsonBytes, err := json.Marshal(source) if err != nil { return err } err = json.Unmarshal(jsonBytes, target) if err != nil { return err } return nil } // StructToMap converts a struct to a map[string]interface{} // Uses json tag name as key when available, so keys match validation schema (e.g. "provider" not "Provider") // does not support nested structs func StructToMap(v any) map[string]interface{} { result := make(map[string]interface{}) val := reflect.ValueOf(v) typ := reflect.TypeOf(v) if val.Kind() == reflect.Ptr { val = val.Elem() typ = typ.Elem() } for i := 0; i < val.NumField(); i++ { field := typ.Field(i) value := val.Field(i) // Skip unexported fields if !value.CanInterface() { continue } key := field.Name if tag := field.Tag.Get("json"); tag != "" { // Use first part before comma (e.g. "provider,omitempty" -> "provider") if name := strings.TrimSpace(strings.Split(tag, ",")[0]); name != "" && name != "-" { key = name } } fieldVal := value.Interface() // If type implements String(), use it so validation gets string (e.g. oauth.Provider -> "mock") // Use reflect to detect nil pointer - s != nil passes for interface holding (*T)(nil) if s, ok := fieldVal.(fmt.Stringer); ok { if isNilValue(value) { result[key] = fieldVal // keep nil/zero for optional fields } else { result[key] = s.String() } } else { result[key] = fieldVal } } return result } // isNilValue returns true if v is nil (ptr, slice, map, chan, func, interface). // Used to avoid calling String() on nil receivers (e.g. *uuid.UUID). func isNilValue(v reflect.Value) bool { switch v.Kind() { case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func, reflect.Interface: return v.IsNil() default: return false } }