package reflectext import ( "errors" "gogs.mikescher.com/BlackForestBytes/goext/langext" "reflect" "strconv" "strings" ) var ErrAccessStructInvalidFieldType = errors.New("invalid field type") var ErrAccessStructFieldInPathWasNil = errors.New("a field in the path was nil") var ErrAccessStructInvalidArrayIndex = errors.New("invalid array index") var ErrAccessStructInvalidMapKey = errors.New("invalid map key") var ErrAccessStructArrayAccess = errors.New("trying to access array") var ErrAccessStructMapAccess = errors.New("trying to access map") var ErrAccessStructMissingField = errors.New("missing field") type AccessStructOpt struct { ReturnNilOnMissingFields bool // return nil (instead of error) when a field in the path is missing (aka the supplied path is wrong) ReturnNilOnNilPtrFields bool // return nil (instead of error) when a field in the path is nil ReturnNilOnWrongFinalFieldType bool // return nil (instead of error) when the (final) field is not of the requested generic type ReturnNilOnWrongIntermedFieldType bool // return nil (instead of error) when the intermediate field has an invalid type ReturnNilOnInvalidArrayIndizes bool // return nil (instead of error) when trying to acces an array with an invalid index (not a number or out of range) ReturnNilOnMissingMapKeys bool // return nil (instead of error) when trying to access a map with a missing key UsedTagForKeys *string // Use this tag for key names in the struct (instead of the StructField.Name) PreventArrayAccess bool // do not access array indizes - throw an error instead PreventMapAccess bool // do not access maps - throw an error instead } func AccessJSONStruct[TResult any](v any, path string) (TResult, error) { return AccessStructByStringPath[TResult](v, path, AccessStructOpt{UsedTagForKeys: langext.Ptr("json")}) } func AccessStruct[TResult any](v any, path string) (TResult, error) { return AccessStructByStringPath[TResult](v, path, AccessStructOpt{}) } func AccessStructByArrayPath[TResult any](v any, path []string, opts ...AccessStructOpt) (TResult, error) { opt := AccessStructOpt{} if len(opts) > 0 { opt = opts[0] } resultVal, err := accessStructByPath(reflect.ValueOf(v), path, opt) if err != nil { return *new(TResult), err } if resultValCast, ok := resultVal.(TResult); ok { return resultValCast, nil } else if opt.ReturnNilOnWrongFinalFieldType { return *new(TResult), nil } else { return *new(TResult), ErrAccessStructInvalidFieldType } } func AccessStructByStringPath[TResult any](v any, path string, opts ...AccessStructOpt) (TResult, error) { opt := AccessStructOpt{} if len(opts) > 0 { opt = opts[0] } arrpath := strings.Split(path, ".") resultVal, err := accessStructByPath(reflect.ValueOf(v), arrpath, opt) if err != nil { return *new(TResult), err } if resultValCast, ok := resultVal.(TResult); ok { return resultValCast, nil } else if opt.ReturnNilOnWrongFinalFieldType { return *new(TResult), nil } else { return *new(TResult), ErrAccessStructInvalidFieldType } } func accessStructByPath(val reflect.Value, path []string, opt AccessStructOpt) (any, error) { if len(path) == 0 { return val.Interface(), nil } currPath := path[0] if val.Kind() == reflect.Ptr { if val.IsNil() { if opt.ReturnNilOnNilPtrFields { return nil, nil } else { return nil, ErrAccessStructFieldInPathWasNil } } return accessStructByPath(val.Elem(), path, opt) } if val.Kind() == reflect.Array || val.Kind() == reflect.Slice { if opt.PreventArrayAccess { return nil, ErrAccessStructArrayAccess } if val.IsNil() { if opt.ReturnNilOnNilPtrFields { return nil, nil } else { return nil, ErrAccessStructFieldInPathWasNil } } arrIdx, err := strconv.ParseInt(currPath, 10, 64) if err != nil { if opt.ReturnNilOnInvalidArrayIndizes { return nil, nil } else { return nil, ErrAccessStructInvalidArrayIndex } } if arrIdx < 0 || int(arrIdx) >= val.Len() { if opt.ReturnNilOnInvalidArrayIndizes { return nil, nil } else { return nil, ErrAccessStructInvalidArrayIndex } } return accessStructByPath(val.Index(int(arrIdx)), path[1:], opt) } if val.Kind() == reflect.Map { if opt.PreventMapAccess { return nil, ErrAccessStructMapAccess } if val.IsNil() { if opt.ReturnNilOnNilPtrFields { return nil, nil } else { return nil, ErrAccessStructFieldInPathWasNil } } mapval := val.MapIndex(reflect.ValueOf(currPath)) if !mapval.IsValid() || mapval.IsZero() { if opt.ReturnNilOnMissingMapKeys { return nil, nil } else { return nil, ErrAccessStructInvalidMapKey } } return accessStructByPath(mapval, path[1:], opt) } if val.Kind() == reflect.Struct { if opt.UsedTagForKeys != nil { for i := 0; i < val.NumField(); i++ { if val.Type().Field(i).Tag.Get(*opt.UsedTagForKeys) == currPath { return accessStructByPath(val.Field(i), path[1:], opt) } } if opt.ReturnNilOnMissingFields { return nil, nil } else { return nil, ErrAccessStructMissingField } } else { for i := 0; i < val.NumField(); i++ { if val.Type().Field(i).Name == currPath { return accessStructByPath(val.Field(i), path[1:], opt) } } if opt.ReturnNilOnMissingFields { return nil, nil } else { return nil, ErrAccessStructMissingField } } } if opt.ReturnNilOnWrongIntermedFieldType { return nil, nil } else { return nil, ErrAccessStructMissingField } }