Last active
February 11, 2020 12:12
-
-
Save antigremlin/7acee4d7017349ec70b1e7b596996504 to your computer and use it in GitHub Desktop.
Go value dereferencing by string path
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 spike | |
import ( | |
"errors" | |
"reflect" | |
"strconv" | |
) | |
var stringType = reflect.TypeOf("") | |
func Deref(dst interface{}, src interface{}, keys []string) (found bool, err error) { | |
defer func() { | |
// Having a recursive algorithm with exceptions is much easier than | |
// maintaining the error return values. I'm not much in love | |
// with the manual error monad Go imposes on us. | |
if msg := recover(); msg != nil { | |
err = errors.New(msg.(string)) | |
} | |
}() | |
result := getValue(reflect.ValueOf(src), keys[0], keys[1:]) | |
if !result.IsValid() { | |
return false, nil | |
} | |
dest := reflect.ValueOf(dst) | |
if dest.Kind() == reflect.Ptr { | |
dval := dest.Elem() | |
// TODO also consider dereferencing the value we've found | |
dval.Set(result.Convert(dval.Type())) | |
} else { | |
return false, errors.New("dst must be a pointer") | |
} | |
return true, nil | |
} | |
func getValue(v reflect.Value, key string, rest []string) reflect.Value { | |
next := index(v, key) | |
if !next.IsValid() || len(rest) == 0 { | |
return next | |
} | |
return getValue(next, rest[0], rest[1:]) | |
} | |
func index(v reflect.Value, key string) reflect.Value { | |
switch v.Kind() { | |
case reflect.Ptr: | |
return index(v.Elem(), key) // invalid if nil | |
case reflect.Slice: | |
idx, err := strconv.Atoi(key) | |
if err != nil { | |
panic("slice index: " + err.Error()) | |
} | |
// TODO check index out of range? | |
return v.Index(idx) // panics if out of range | |
case reflect.Map: | |
if v.Type().Key() != reflect.TypeOf("") { | |
panic("deref.index: map key should be string") | |
} | |
return v.MapIndex(reflect.ValueOf(key)) // invalid if not found | |
case reflect.Struct: | |
// TODO use the cache of names to field indices, built from structu tags, etc. | |
return v.FieldByName(key) // invalid if not found | |
} | |
panic("can't index: " + v.Kind().String()) | |
} |
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 spike | |
import "testing" | |
func TestSlice0(t *testing.T) { | |
src := []int{1, 2, 3} | |
var dst float64 | |
found, err := Deref(&dst, src, []string{"0"}) | |
if found { | |
t.Log("Returned:", dst) | |
} else { | |
t.Error(err) | |
} | |
} | |
func TestMap(t *testing.T) { | |
src := map[string]int{"foo": 1, "bar": 2} | |
// src := map[int]int{1: 1, 2: 2} | |
var dst int | |
found, err := Deref(&dst, src, []string{"foo"}) | |
if found { | |
t.Log("Returned:", dst) | |
} else { | |
t.Error(err) | |
} | |
} | |
func TestMapStr(t *testing.T) { | |
src := map[string]string{"foo": "one", "bar": "two"} | |
var dst []byte | |
found, err := Deref(&dst, src, []string{"foo"}) | |
if found { | |
t.Log("Returned:", dst) | |
} else { | |
t.Error(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment