package accessor import ( "errors" "fmt" "reflect" ) // StructAccessor is a json string with get functions. type StructAccessor struct { object reflect.Value } // NewStructAccessor adds the Accessor interface to a JSON string. func NewStructAccessor(object interface{}) *StructAccessor { return &StructAccessor{ object: reflect.ValueOf(object).Elem(), } } // Set sets the value identified by key. func (sa *StructAccessor) Set(key string, value interface{}) error { field := sa.object.FieldByName(key) if !field.IsValid() { return errors.New("struct field does not exist") } if !field.CanSet() { return fmt.Errorf("field %s or struct is immutable", field.String()) } newVal := reflect.ValueOf(value) // set directly if type matches if newVal.Kind() == field.Kind() { field.Set(newVal) return nil } // handle special cases switch field.Kind() { // nolint:exhaustive // ints case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var newInt int64 switch newVal.Kind() { // nolint:exhaustive case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: newInt = newVal.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: newInt = int64(newVal.Uint()) default: return fmt.Errorf("tried to set field %s (%s) to a %s value", key, field.Kind().String(), newVal.Kind().String()) } if field.OverflowInt(newInt) { return fmt.Errorf("setting field %s (%s) to %d would overflow", key, field.Kind().String(), newInt) } field.SetInt(newInt) // uints case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var newUint uint64 switch newVal.Kind() { // nolint:exhaustive case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: newUint = uint64(newVal.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: newUint = newVal.Uint() default: return fmt.Errorf("tried to set field %s (%s) to a %s value", key, field.Kind().String(), newVal.Kind().String()) } if field.OverflowUint(newUint) { return fmt.Errorf("setting field %s (%s) to %d would overflow", key, field.Kind().String(), newUint) } field.SetUint(newUint) // floats case reflect.Float32, reflect.Float64: switch newVal.Kind() { // nolint:exhaustive case reflect.Float32, reflect.Float64: field.SetFloat(newVal.Float()) default: return fmt.Errorf("tried to set field %s (%s) to a %s value", key, field.Kind().String(), newVal.Kind().String()) } default: return fmt.Errorf("tried to set field %s (%s) to a %s value", key, field.Kind().String(), newVal.Kind().String()) } return nil } // Get returns the value found by the given json key and whether it could be successfully extracted. func (sa *StructAccessor) Get(key string) (value interface{}, ok bool) { field := sa.object.FieldByName(key) if !field.IsValid() || !field.CanInterface() { return nil, false } return field.Interface(), true } // GetString returns the string found by the given json key and whether it could be successfully extracted. func (sa *StructAccessor) GetString(key string) (value string, ok bool) { field := sa.object.FieldByName(key) if !field.IsValid() || field.Kind() != reflect.String { return "", false } return field.String(), true } // GetStringArray returns the []string found by the given json key and whether it could be successfully extracted. func (sa *StructAccessor) GetStringArray(key string) (value []string, ok bool) { field := sa.object.FieldByName(key) if !field.IsValid() || field.Kind() != reflect.Slice || !field.CanInterface() { return nil, false } v := field.Interface() slice, ok := v.([]string) if !ok { return nil, false } return slice, true } // GetInt returns the int found by the given json key and whether it could be successfully extracted. func (sa *StructAccessor) GetInt(key string) (value int64, ok bool) { field := sa.object.FieldByName(key) if !field.IsValid() { return 0, false } switch field.Kind() { // nolint:exhaustive case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int(), true case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return int64(field.Uint()), true default: return 0, false } } // GetFloat returns the float found by the given json key and whether it could be successfully extracted. func (sa *StructAccessor) GetFloat(key string) (value float64, ok bool) { field := sa.object.FieldByName(key) if !field.IsValid() { return 0, false } switch field.Kind() { // nolint:exhaustive case reflect.Float32, reflect.Float64: return field.Float(), true default: return 0, false } } // GetBool returns the bool found by the given json key and whether it could be successfully extracted. func (sa *StructAccessor) GetBool(key string) (value bool, ok bool) { field := sa.object.FieldByName(key) if !field.IsValid() || field.Kind() != reflect.Bool { return false, false } return field.Bool(), true } // Exists returns the whether the given key exists. func (sa *StructAccessor) Exists(key string) bool { field := sa.object.FieldByName(key) return field.IsValid() } // Type returns the accessor type as a string. func (sa *StructAccessor) Type() string { return "StructAccessor" }