// Copyright © 2013 Théo Crevon
//
// See the file LICENSE for copying permission.

// Package reflections provides high level abstractions over the Go standard [reflect] library. // // In practice, the `reflect` library's API proves somewhat low-level and un-intuitive. // Using it can turn out pretty complex, daunting, and scary, when doing simple // things like accessing a structure field value, a field tag, etc. // // The `reflections` package aims to make developers' life easier when it comes to introspect // struct values at runtime. Its API takes inspiration in the python language's `getattr,` `setattr,` and `hasattr` set // of methods and provides simplified access to structure fields and tags. // // [reflect]: http://golang.org/pkg/reflect/
package reflections import ( ) // ErrUnsupportedType indicates that the provided type doesn't support the requested reflection operation. var ErrUnsupportedType = errors.New("unsupported type") // ErrUnexportedField indicates that an operation failed as a result of // applying to a non-exported struct field. var ErrUnexportedField = errors.New("unexported field") // GetField returns the value of the provided obj field. // The `obj` can either be a structure or pointer to structure. func ( interface{}, string) (interface{}, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use GetField on a non-struct object: %w", ErrUnsupportedType) } := reflectValue() := .FieldByName() if !.IsValid() { return nil, fmt.Errorf("no such field: %s in obj", ) } return .Interface(), nil } // GetFieldKind returns the kind of the provided obj field. // The `obj` can either be a structure or pointer to structure. func ( interface{}, string) (reflect.Kind, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return reflect.Invalid, fmt.Errorf("cannot use GetFieldKind on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .FieldByName() if !.IsValid() { return reflect.Invalid, fmt.Errorf("no such field: %s in obj", ) } return .Type().Kind(), nil } // GetFieldType returns the kind of the provided obj field. // The `obj` can either be a structure or pointer to structure. func ( interface{}, string) (string, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", fmt.Errorf("cannot use GetFieldType on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .FieldByName() if !.IsValid() { return "", fmt.Errorf("no such field: %s in obj", ) } return .Type().String(), nil } // GetFieldTag returns the provided obj field tag value. // The `obj` parameter can either be a structure or pointer to structure. func ( interface{}, , string) (string, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", fmt.Errorf("cannot use GetFieldTag on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .Type() , := .FieldByName() if ! { return "", fmt.Errorf("no such field: %s in obj", ) } if !isExportableField() { return "", fmt.Errorf("cannot GetFieldTag on a non-exported struct field: %w", ErrUnexportedField) } return .Tag.Get(), nil } // GetFieldNameByTagValue looks up a field with a matching `{tagKey}:"{tagValue}"` tag in the provided `obj` item. // The `obj` parameter must be a `struct`, or a `pointer` to one. If the `obj` parameter doesn't have a field tagged // with the `tagKey`, and the matching `tagValue`, this function returns an error. func ( interface{}, , string) (string, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return "", fmt.Errorf("cannot use GetFieldByTag on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .Type() := .NumField() for := range { := .Field() if .Tag.Get() == { return .Name, nil } } return "", errors.New("tag doesn't exist in the given struct") } // SetField sets the provided obj field with provided value. // // The `obj` parameter must be a pointer to a struct, otherwise it soundly fails. // The provided `value` type should match with the struct field being set. func ( interface{}, string, interface{}) error { // Fetch the field reflect.Value := reflect.ValueOf().Elem() := .FieldByName() if !.IsValid() { return fmt.Errorf("no such field: %s in obj", ) } if !.CanSet() { return fmt.Errorf("cannot set %s field value", ) } := .Type() := reflect.ValueOf() if !.Type().AssignableTo() { := errors.New("provided value type not assignable to obj field type") return } .Set() return nil } // HasField checks if the provided `obj` struct has field named `name`. // The `obj` can either be a structure or pointer to structure. func ( interface{}, string) (bool, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return false, fmt.Errorf("cannot use HasField on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .Type() , := .FieldByName() if ! || !isExportableField() { return false, nil } return true, nil } // Fields returns the struct fields names list. // The `obj` parameter can either be a structure or pointer to structure. func ( interface{}) ([]string, error) { return fields(, false) } // FieldsDeep returns "flattened" fields. // // Note that FieldsDeep treats fields from anonymous inner structs as normal fields. func ( interface{}) ([]string, error) { return fields(, true) } func fields( interface{}, bool) ([]string, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use fields on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .Type() := .NumField() var []string for := range { := .Field() if isExportableField() { if ! || !.Anonymous { = append(, .Name) continue } := .Field() , := (.Interface(), ) if != nil { return nil, fmt.Errorf("cannot get fields in %s: %w", .Name, ) } = append(, ...) } } return , nil } // Items returns the field:value struct pairs as a map. // The `obj` parameter can either be a structure or pointer to structure. func ( interface{}) (map[string]interface{}, error) { return items(, false) } // ItemsDeep returns "flattened" items. // Note that ItemsDeep will treat fields from anonymous inner structs as normal fields. func ( interface{}) (map[string]interface{}, error) { return items(, true) } func items( interface{}, bool) (map[string]interface{}, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use items on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .Type() := .NumField() := make(map[string]interface{}) for := range { := .Field() := .Field() if isExportableField() { if ! || !.Anonymous { [.Name] = .Interface() continue } , := (.Interface(), ) if != nil { return nil, fmt.Errorf("cannot get items in %s: %w", .Name, ) } for , := range { [] = } } } return , nil } // Tags lists the struct tag fields. // The `obj` can whether be a structure or pointer to structure. func ( interface{}, string) (map[string]string, error) { return tags(, , false) } // TagsDeep returns "flattened" tags. // Note that TagsDeep treats fields from anonymous // inner structs as normal fields. func ( interface{}, string) (map[string]string, error) { return tags(, , true) } func tags( interface{}, string, bool) (map[string]string, error) { if !isSupportedType(, []reflect.Kind{reflect.Struct, reflect.Ptr}) { return nil, fmt.Errorf("cannot use tags on a non-struct interface: %w", ErrUnsupportedType) } := reflectValue() := .Type() := .NumField() := make(map[string]string) for := range { := .Field() if isExportableField() { if ! || !.Anonymous { [.Name] = .Tag.Get() continue } := .Field() , := (.Interface(), , ) if != nil { return nil, fmt.Errorf("cannot get items in %s: %w", .Name, ) } for , := range { [] = } } } return , nil } func reflectValue( interface{}) reflect.Value { var reflect.Value if reflect.TypeOf().Kind() == reflect.Ptr { = reflect.ValueOf().Elem() } else { = reflect.ValueOf() } return } func isExportableField( reflect.StructField) bool { // PkgPath is empty for exported fields. return .PkgPath == "" } func isSupportedType( interface{}, []reflect.Kind) bool { for , := range { if reflect.TypeOf().Kind() == { return true } } return false }