package reflections
import (
"errors"
"fmt"
"reflect"
)
var ErrUnsupportedType = errors .New ("unsupported type" )
var ErrUnexportedField = errors .New ("unexported field" )
func GetField (obj interface {}, name string ) (interface {}, error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return nil , fmt .Errorf ("cannot use GetField on a non-struct object: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
field := objValue .FieldByName (name )
if !field .IsValid () {
return nil , fmt .Errorf ("no such field: %s in obj" , name )
}
return field .Interface (), nil
}
func GetFieldKind (obj interface {}, name string ) (reflect .Kind , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return reflect .Invalid , fmt .Errorf ("cannot use GetFieldKind on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
field := objValue .FieldByName (name )
if !field .IsValid () {
return reflect .Invalid , fmt .Errorf ("no such field: %s in obj" , name )
}
return field .Type ().Kind (), nil
}
func GetFieldType (obj interface {}, name string ) (string , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return "" , fmt .Errorf ("cannot use GetFieldType on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
field := objValue .FieldByName (name )
if !field .IsValid () {
return "" , fmt .Errorf ("no such field: %s in obj" , name )
}
return field .Type ().String (), nil
}
func GetFieldTag (obj interface {}, fieldName , tagKey string ) (string , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return "" , fmt .Errorf ("cannot use GetFieldTag on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
objType := objValue .Type ()
field , ok := objType .FieldByName (fieldName )
if !ok {
return "" , fmt .Errorf ("no such field: %s in obj" , fieldName )
}
if !isExportableField (field ) {
return "" , fmt .Errorf ("cannot GetFieldTag on a non-exported struct field: %w" , ErrUnexportedField )
}
return field .Tag .Get (tagKey ), nil
}
func GetFieldNameByTagValue (obj interface {}, tagKey , tagValue string ) (string , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return "" , fmt .Errorf ("cannot use GetFieldByTag on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
objType := objValue .Type ()
fieldsCount := objType .NumField ()
for i := range fieldsCount {
structField := objType .Field (i )
if structField .Tag .Get (tagKey ) == tagValue {
return structField .Name , nil
}
}
return "" , errors .New ("tag doesn't exist in the given struct" )
}
func SetField (obj interface {}, name string , value interface {}) error {
structValue := reflect .ValueOf (obj ).Elem ()
structFieldValue := structValue .FieldByName (name )
if !structFieldValue .IsValid () {
return fmt .Errorf ("no such field: %s in obj" , name )
}
if !structFieldValue .CanSet () {
return fmt .Errorf ("cannot set %s field value" , name )
}
structFieldType := structFieldValue .Type ()
val := reflect .ValueOf (value )
if !val .Type ().AssignableTo (structFieldType ) {
invalidTypeError := errors .New ("provided value type not assignable to obj field type" )
return invalidTypeError
}
structFieldValue .Set (val )
return nil
}
func HasField (obj interface {}, name string ) (bool , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return false , fmt .Errorf ("cannot use HasField on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
objType := objValue .Type ()
field , ok := objType .FieldByName (name )
if !ok || !isExportableField (field ) {
return false , nil
}
return true , nil
}
func Fields (obj interface {}) ([]string , error ) {
return fields (obj , false )
}
func FieldsDeep (obj interface {}) ([]string , error ) {
return fields (obj , true )
}
func fields(obj interface {}, deep bool ) ([]string , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return nil , fmt .Errorf ("cannot use fields on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
objType := objValue .Type ()
fieldsCount := objType .NumField ()
var allFields []string
for i := range fieldsCount {
field := objType .Field (i )
if isExportableField (field ) {
if !deep || !field .Anonymous {
allFields = append (allFields , field .Name )
continue
}
fieldValue := objValue .Field (i )
subFields , err := fields (fieldValue .Interface (), deep )
if err != nil {
return nil , fmt .Errorf ("cannot get fields in %s: %w" , field .Name , err )
}
allFields = append (allFields , subFields ...)
}
}
return allFields , nil
}
func Items (obj interface {}) (map [string ]interface {}, error ) {
return items (obj , false )
}
func ItemsDeep (obj interface {}) (map [string ]interface {}, error ) {
return items (obj , true )
}
func items(obj interface {}, deep bool ) (map [string ]interface {}, error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return nil , fmt .Errorf ("cannot use items on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
objType := objValue .Type ()
fieldsCount := objType .NumField ()
allItems := make (map [string ]interface {})
for i := range fieldsCount {
field := objType .Field (i )
fieldValue := objValue .Field (i )
if isExportableField (field ) {
if !deep || !field .Anonymous {
allItems [field .Name ] = fieldValue .Interface ()
continue
}
m , err := items (fieldValue .Interface (), deep )
if err != nil {
return nil , fmt .Errorf ("cannot get items in %s: %w" , field .Name , err )
}
for k , v := range m {
allItems [k ] = v
}
}
}
return allItems , nil
}
func Tags (obj interface {}, key string ) (map [string ]string , error ) {
return tags (obj , key , false )
}
func TagsDeep (obj interface {}, key string ) (map [string ]string , error ) {
return tags (obj , key , true )
}
func tags(obj interface {}, key string , deep bool ) (map [string ]string , error ) {
if !isSupportedType (obj , []reflect .Kind {reflect .Struct , reflect .Ptr }) {
return nil , fmt .Errorf ("cannot use tags on a non-struct interface: %w" , ErrUnsupportedType )
}
objValue := reflectValue (obj )
objType := objValue .Type ()
fieldsCount := objType .NumField ()
allTags := make (map [string ]string )
for i := range fieldsCount {
structField := objType .Field (i )
if isExportableField (structField ) {
if !deep || !structField .Anonymous {
allTags [structField .Name ] = structField .Tag .Get (key )
continue
}
fieldValue := objValue .Field (i )
m , err := tags (fieldValue .Interface (), key , deep )
if err != nil {
return nil , fmt .Errorf ("cannot get items in %s: %w" , structField .Name , err )
}
for k , v := range m {
allTags [k ] = v
}
}
}
return allTags , nil
}
func reflectValue(obj interface {}) reflect .Value {
var val reflect .Value
if reflect .TypeOf (obj ).Kind () == reflect .Ptr {
val = reflect .ValueOf (obj ).Elem ()
} else {
val = reflect .ValueOf (obj )
}
return val
}
func isExportableField(field reflect .StructField ) bool {
return field .PkgPath == ""
}
func isSupportedType(obj interface {}, types []reflect .Kind ) bool {
for _ , t := range types {
if reflect .TypeOf (obj ).Kind () == t {
return true
}
}
return false
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .