Skip to content

Instantly share code, notes, and snippets.

@mjhuber
Last active February 5, 2020 13:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mjhuber/b538a0dba266c349922d59da5f038d9f to your computer and use it in GitHub Desktop.
Save mjhuber/b538a0dba266c349922d59da5f038d9f to your computer and use it in GitHub Desktop.
We All Love Reflection
package main
import (
"fmt"
"reflect"
"regexp"
"strings"
)
// Test is a test struct
type Test struct {
Name string `json:"name"`
Description string `json:"description"`
Nested *NestedObject `json:"nested"`
}
// NestedObject is an object that is nested
type NestedObject struct {
NestedName string `json:"nested_name"`
NestedDescription string `json:"nested_description"`
}
func main() {
t := Test{
Name: "Test Name",
Description: "Test Description",
Nested: &NestedObject{
NestedName: "Nested Name",
NestedDescription: "Nested Description",
},
}
// this works
setProperty("name", &t, "This name has been altered.")
fmt.Printf("\n\n%+v\n\n", t)
/*
This does not:
panic: reflect: call of reflect.Value.NumField on ptr Value
*/
//setProperty("nested.nested_name", &t, "This is a nested name")
//fmt.Printf("\n\n%+v\n\n", t)
}
// setProperty sets the value field obj to value val
func setProperty(name string, obj interface{}, val interface{}) {
parts := strings.Split(name, ".")
parent := reflect.ValueOf(obj)
for i, field := range parts {
current := getReflectedField(field, parent)
if i == len(parts)-1 {
// reached the final object - set the value
v := reflect.Indirect(current)
fmt.Printf("Setting value to: \"%v\", kind is: %s\n", val, v.Kind())
switch v.Kind() {
case reflect.Int:
num, ok := val.(int)
if ok {
v.SetInt(int64(num))
}
case reflect.String:
str, ok := val.(string)
if ok {
v.SetString(str)
}
case reflect.Bool:
b, ok := val.(bool)
if ok {
v.SetBool(b)
}
case reflect.Ptr:
fieldType := v.Type()
newVal := ptr(reflect.ValueOf(val))
s := newVal.Convert(fieldType)
v.Set(s)
}
} else {
parent = current
}
}
}
func ptr(v reflect.Value) reflect.Value {
pt := reflect.PtrTo(v.Type()) // create a *T type.
pv := reflect.New(pt.Elem()) // create a reflect.Value of type *T.
pv.Elem().Set(v) // sets pv to point to underlying value of v.
return pv
}
func getReflectedField(name string, v reflect.Value) reflect.Value {
r := v.Elem()
fmt.Printf("Type is: %s\n", r.Type())
fmt.Printf("Kind is: %s\n\n", r.Kind())
for i := 0; i < r.NumField(); i++ {
fName := r.Type().Field(i).Name
tags := r.Type().Field(i).Tag
if matches(fName, name, string(tags)) {
// it's the field we want
v = reflect.Indirect(v)
return v.Field(i).Addr()
}
}
return reflect.Value{}
}
// returns true if fieldName either matches the name of the field of the json/yaml tags match
func matches(fieldName string, desiredName string, tags string) bool {
if strings.ToLower(fieldName) == strings.ToLower(desiredName) {
return true
}
// check json/yaml field name
var re = regexp.MustCompile(`(?m)(json|yaml):\"[a-zA-Z]+(,.+|\")`)
return re.MatchString(desiredName)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment