Last active
November 19, 2022 20:11
-
-
Save oslyak/081e6462570ce2af417fddd7db97c082 to your computer and use it in GitHub Desktop.
reflect.go Get and Set struct field by Name
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package utils | |
import ( | |
"errors" | |
"fmt" | |
"reflect" | |
) | |
var ( | |
ErrReflectStructPointerExpected = errors.New("pointer to struct expected") | |
ErrReflectFieldDoesNotExist = errors.New("provided field does not exists") | |
ErrReflectPrivateField = errors.New("can not set private field") | |
ErrReflectGenerictType = errors.New("generic type does not match type of provided field") | |
) | |
// Set struct field by fieldName to value. | |
// structPtr - should be poiter ro struct. | |
func SetField(structPtr interface{}, fieldName string, value interface{}) error { | |
if reflect.TypeOf(structPtr).Kind() != reflect.Ptr { | |
return ErrReflectStructPointerExpected | |
} | |
if reflect.TypeOf(structPtr).Elem().Kind() != reflect.Struct { | |
return ErrReflectStructPointerExpected | |
} | |
pointerToStruct := reflect.ValueOf(structPtr) | |
strct := pointerToStruct.Elem() | |
field := strct.FieldByName(fieldName) // type: reflect.Value | |
if !field.IsValid() { | |
return fmt.Errorf(`%w: %s`, ErrReflectFieldDoesNotExist, fieldName) | |
} | |
if !field.CanSet() { | |
return fmt.Errorf(`%w: %s`, ErrReflectPrivateField, fieldName) | |
} | |
field.Set(reflect.ValueOf(value)) | |
return nil | |
} | |
// Get struct field by fieldName to value. | |
// structPtr - should be poiter ro struct. | |
// Generic tyoe - should be type of struct field. | |
func GetField[T any](structPtr interface{}, fieldName string) (T, error) { | |
var result T | |
if reflect.TypeOf(structPtr).Kind() != reflect.Ptr { | |
return result, ErrReflectStructPointerExpected | |
} | |
if reflect.TypeOf(structPtr).Elem().Kind() != reflect.Struct { | |
return result, ErrReflectStructPointerExpected | |
} | |
pointerToStruct := reflect.ValueOf(structPtr) | |
strct := pointerToStruct.Elem() | |
field := strct.FieldByName(fieldName) // type: reflect.Value | |
if !field.IsValid() { | |
return result, fmt.Errorf(`%w: %s`, ErrReflectFieldDoesNotExist, fieldName) | |
} | |
if !field.CanSet() { | |
return result, fmt.Errorf(`%w: %s`, ErrReflectPrivateField, fieldName) | |
} | |
result, ok := field.Interface().(T) | |
if !ok { | |
return result, ErrReflectGenerictType | |
} | |
return result, nil | |
} | |
// Get string with Generic T type name. | |
func GetTypeName[T any]() string { | |
var obj T | |
return reflect.TypeOf(obj).String() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package utils_test | |
import ( | |
"testing" | |
"newone/pkg/utils" | |
"github.com/stretchr/testify/require" | |
) | |
type userType struct { | |
ID int64 | |
FullName string | |
Login string | |
Age int | |
private string | |
} | |
func TestSetField(t *testing.T) { | |
var ( | |
err error | |
user userType | |
expect userType | |
) | |
user = userType{ID: 73, FullName: "Super Man", Login: "superman", Age: 43, private: "secret data"} | |
expect = userType{ID: 42, FullName: "Clark Joseph Kent", Login: "c.kent", Age: 36, private: "another private data"} | |
err = utils.SetField(user, `Age`, expect.Age) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectStructPointerExpected) | |
err = utils.SetField(new(string), `Age`, expect.Age) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectStructPointerExpected) | |
err = utils.SetField(&user, `InvalidField`, expect.Age) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectFieldDoesNotExist) | |
err = utils.SetField(&user, `private`, expect.private) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectPrivateField) | |
err = utils.SetField(&user, `FullName`, expect.FullName) | |
require.NoError(t, err) | |
require.Equal(t, expect.FullName, user.FullName) | |
err = utils.SetField(&user, `Login`, expect.Login) | |
require.NoError(t, err) | |
require.Equal(t, expect.Login, user.Login) | |
err = utils.SetField(&user, `Age`, expect.Age) | |
require.NoError(t, err) | |
require.Equal(t, expect.Age, user.Age) | |
err = utils.SetField(&user, `ID`, expect.ID) | |
require.NoError(t, err) | |
require.Equal(t, expect.ID, user.ID) | |
} | |
func TestGetField(t *testing.T) { | |
var ( | |
err error | |
user userType | |
value any | |
) | |
user = userType{ID: 42, FullName: "Clark Joseph Kent", Login: "c.kent", Age: 36, private: "another private data"} | |
value, err = utils.GetField[string](user, `Age`) | |
require.Empty(t, value) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectStructPointerExpected) | |
value, err = utils.GetField[string](new(string), `Age`) | |
require.Empty(t, value) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectStructPointerExpected) | |
value, err = utils.GetField[string](&user, `InvalidField`) | |
require.Empty(t, value) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectFieldDoesNotExist) | |
value, err = utils.GetField[string](&user, `private`) | |
require.Empty(t, value) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectPrivateField) | |
value, err = utils.GetField[string](&user, `Age`) | |
require.Empty(t, value) | |
require.Error(t, err) | |
require.ErrorIs(t, err, utils.ErrReflectGenerictType) | |
value, err = utils.GetField[string](&user, `FullName`) | |
require.NoError(t, err) | |
require.Equal(t, value, user.FullName) | |
value, err = utils.GetField[string](&user, `Login`) | |
require.NoError(t, err) | |
require.Equal(t, value, user.Login) | |
value, err = utils.GetField[int](&user, `Age`) | |
require.NoError(t, err) | |
require.Equal(t, value, user.Age) | |
value, err = utils.GetField[int64](&user, `ID`) | |
require.NoError(t, err) | |
require.Equal(t, value, user.ID) | |
} | |
func TestGetTypeName(t *testing.T) { | |
var typeName string | |
typeName = utils.GetTypeName[string]() | |
require.Equal(t, `string`, typeName) | |
typeName = utils.GetTypeName[uintptr]() | |
require.Equal(t, `uintptr`, typeName) | |
typeName = utils.GetTypeName[int]() | |
require.Equal(t, `int`, typeName) | |
typeName = utils.GetTypeName[int8]() | |
require.Equal(t, `int8`, typeName) | |
typeName = utils.GetTypeName[int16]() | |
require.Equal(t, `int16`, typeName) | |
typeName = utils.GetTypeName[int32]() | |
require.Equal(t, `int32`, typeName) | |
typeName = utils.GetTypeName[int64]() | |
require.Equal(t, `int64`, typeName) | |
typeName = utils.GetTypeName[userType]() | |
require.Equal(t, `utils_test.userType`, typeName) | |
typeName = utils.GetTypeName[*userType]() | |
require.Equal(t, `*utils_test.userType`, typeName) | |
typeName = utils.GetTypeName[[]userType]() | |
require.Equal(t, `[]utils_test.userType`, typeName) | |
typeName = utils.GetTypeName[[]*userType]() | |
require.Equal(t, `[]*utils_test.userType`, typeName) | |
typeName = utils.GetTypeName[struct{}]() | |
require.Equal(t, `struct {}`, typeName) | |
typeName = utils.GetTypeName[*struct{}]() | |
require.Equal(t, `*struct {}`, typeName) | |
typeName = utils.GetTypeName[[]struct{}]() | |
require.Equal(t, `[]struct {}`, typeName) | |
typeName = utils.GetTypeName[[]*struct{}]() | |
require.Equal(t, `[]*struct {}`, typeName) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment