80 lines
1.9 KiB
Go
80 lines
1.9 KiB
Go
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
|
|
}
|
|
}
|