Last active
July 9, 2020 11:42
-
-
Save blakelead/10e31bd46875323430bba96b75eaf66f to your computer and use it in GitHub Desktop.
Learning reflection in go by mimicking yaml.Unmarshal
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
field: value |
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 main | |
import ( | |
"fmt" | |
"reflect" | |
) | |
type Config struct { | |
Field string `file:"field"` | |
} | |
func main() { | |
blob, _ := read("config.yaml") | |
var in interface{} | |
yaml.Unmarshal(blob, &in) | |
config := Config{} | |
Unmarshal(in, &config) | |
} | |
// Unmarshal populates `dst` structure with `in` data | |
func Unmarshal(in interface{}, dst interface{}) { | |
src := FlattenInterface(&in) | |
obj := reflect.ValueOf(dst) | |
unmarshalWithPrefix(src, obj, "") | |
} | |
func unmarshalWithPrefix(src map[string]interface{}, obj reflect.Value, prefix string) { | |
switch obj.Kind() { | |
case reflect.Ptr, reflect.Interface: | |
val := obj.Elem() | |
unmarshalWithPrefix(src, val, "") | |
case reflect.Struct: | |
for i := 0; i < obj.NumField(); i++ { | |
if name, ok := obj.Type().Field(i).Tag.Lookup("file"); ok { | |
unmarshalWithPrefix(src, obj.Field(i), fmt.Sprintf("%s.%s", prefix, name)) | |
} | |
} | |
case reflect.Slice: | |
if val, found := src[prefix]; found { | |
v := reflect.ValueOf(val) | |
if v.Kind() == reflect.Slice { | |
obj.Set(reflect.MakeSlice(obj.Type(), v.Len(), v.Cap())) | |
for i := 0; i < v.Len(); i++ { | |
unmarshalWithPrefix(src, obj.Index(i), fmt.Sprintf("%s.%d", prefix, i)) | |
} | |
} | |
} | |
case reflect.String: | |
if val, found := src[prefix]; found { | |
v := reflect.ValueOf(val) | |
if v.Kind() == reflect.String { | |
obj.SetString(v.String()) | |
} | |
} | |
case reflect.Float64: | |
if val, found := src[prefix]; found { | |
v := reflect.ValueOf(val) | |
if v.Kind() == reflect.Float64 { | |
obj.SetFloat(v.Float()) | |
} | |
} | |
case reflect.Int: | |
if val, found := src[prefix]; found { | |
v := reflect.ValueOf(val) | |
if v.Kind() == reflect.Int { | |
obj.SetInt(v.Int()) | |
} | |
} | |
case reflect.Bool: | |
if val, found := src[prefix]; found { | |
v := reflect.ValueOf(val) | |
if v.Kind() == reflect.Bool { | |
obj.SetBool(v.Bool()) | |
} | |
} | |
} | |
} | |
// FlattenInterface takes an interface and flatten it | |
// into a map with key being a string representation of | |
// field path. e.g. ".param.nested" -> "value" | |
func FlattenInterface(in interface{}) map[string]interface{} { | |
obj := reflect.ValueOf(&in) | |
return flattenWithPrefix(obj, "") | |
} | |
func flattenWithPrefix(obj reflect.Value, prefix string) map[string]interface{} { | |
o := make(map[string]interface{}) | |
switch obj.Kind() { | |
case reflect.Ptr: | |
res := flattenWithPrefix(obj.Elem(), "") | |
o = mergeMaps(o, res) | |
case reflect.Interface: | |
res := flattenWithPrefix(obj.Elem(), prefix) | |
o = mergeMaps(o, res) | |
case reflect.Map: | |
for _, key := range obj.MapKeys() { | |
res := flattenWithPrefix(obj.MapIndex(key), fmt.Sprintf("%s.%s", prefix, key.Elem().String())) | |
o = mergeMaps(o, res) | |
} | |
case reflect.Slice: | |
o[prefix] = obj.Interface() | |
for i := 0; i < obj.Len(); i++ { | |
res := flattenWithPrefix(obj.Index(i), fmt.Sprintf("%s.%d", prefix, i)) | |
o = mergeMaps(o, res) | |
} | |
case reflect.String: | |
o[prefix] = obj.Interface() | |
case reflect.Float64: | |
o[prefix] = obj.Interface() | |
case reflect.Int: | |
o[prefix] = obj.Interface() | |
case reflect.Bool: | |
o[prefix] = obj.Interface() | |
} | |
return o | |
} | |
func mergeMaps(a, b map[string]interface{}) map[string]interface{} { | |
for k, v := range b { | |
a[k] = v | |
} | |
return a | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment