Files
base/pkg/reflectutil/structpopulate.go
2026-04-10 18:25:21 +03:30

197 lines
4.0 KiB
Go

package reflectutil
import (
"fmt"
"reflect"
"strings"
)
// ValueGetter wraps a map[string]any and implements ValueGetter
type ValueGetter map[string]any
// Get retrieves a value from the map by key
func (m ValueGetter) Get(key string) (any, bool) {
v, ok := m[key]
return v, ok
}
// GetFloat64 retrieves a float64 value from the map by key
func (m ValueGetter) GetFloat64(key string) (float64, bool) {
v, ok := m.Get(key)
if !ok {
return 0, false
}
switch val := v.(type) {
case float64:
return val, true
case float32:
return float64(val), true
case int:
return float64(val), true
case int64:
return float64(val), true
case int32:
return float64(val), true
default:
return 0, false
}
}
// GetInt retrieves an int value from the map by key
func (m ValueGetter) GetInt(key string) (int, bool) {
v, ok := m.Get(key)
if !ok {
return 0, false
}
switch val := v.(type) {
case int:
return val, true
case int64:
return int(val), true
case int32:
return int(val), true
case float64:
return int(val), true
case float32:
return int(val), true
default:
return 0, false
}
}
// GetString retrieves a string value from the map by key
func (m ValueGetter) GetString(key string) (string, bool) {
v, ok := m.Get(key)
if !ok {
return "", false
}
switch val := v.(type) {
case string:
return val, true
default:
return "", false
}
}
// GetJSONTagName extracts the JSON tag name from a struct field
func GetJSONTagName(field reflect.StructField) string {
tag := field.Tag.Get("json")
if tag == "" || tag == "-" {
return ""
}
// Handle cases like `json:"name,omitempty"` - take only the first part
if idx := strings.Index(tag, ","); idx != -1 {
tag = tag[:idx]
}
return tag
}
// getFloat64FromAny converts an any value to float64
func getFloat64FromAny(v any) (float64, bool) {
switch val := v.(type) {
case float64:
return val, true
case float32:
return float64(val), true
case int:
return float64(val), true
case int64:
return float64(val), true
case int32:
return float64(val), true
default:
return 0, false
}
}
// getIntFromAny converts an any value to int
func getIntFromAny(v any) (int, bool) {
switch val := v.(type) {
case int:
return val, true
case int64:
return int(val), true
case int32:
return int(val), true
case float64:
return int(val), true
case float32:
return int(val), true
default:
return 0, false
}
}
// getStringFromAny converts an any value to string
func getStringFromAny(v any) (string, bool) {
switch val := v.(type) {
case string:
return val, true
default:
return "", false
}
}
// PopulateStructFromMap populates a struct from a map[string]any using reflection
// The target must be a pointer to a struct
func PopulateStructFromMap(m map[string]any, target interface{}) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("target must be a pointer to a struct")
}
v = v.Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
if !field.CanSet() {
continue
}
jsonTag := GetJSONTagName(fieldType)
if jsonTag == "" {
continue
}
mapVal, ok := m[jsonTag]
if !ok {
return fmt.Errorf("%s not found in map", jsonTag)
}
fieldKind := field.Kind()
switch fieldKind {
case reflect.Float64:
val, ok := getFloat64FromAny(mapVal)
if !ok {
return fmt.Errorf("cannot convert %s to float64 for field %s", reflect.TypeOf(mapVal), jsonTag)
}
field.SetFloat(val)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, ok := getIntFromAny(mapVal)
if !ok {
return fmt.Errorf("cannot convert %s to int for field %s", reflect.TypeOf(mapVal), jsonTag)
}
field.SetInt(int64(val))
case reflect.String:
val, ok := getStringFromAny(mapVal)
if !ok {
return fmt.Errorf("cannot convert %s to string for field %s", reflect.TypeOf(mapVal), jsonTag)
}
field.SetString(val)
default:
return fmt.Errorf("unsupported field type %s for field %s", fieldKind, jsonTag)
}
}
return nil
}