Last active
February 5, 2020 13:49
-
-
Save mjhuber/b538a0dba266c349922d59da5f038d9f to your computer and use it in GitHub Desktop.
We All Love Reflection
This file contains 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 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