This repository contains a bunch of examples for dealing with the reflect
package.
Mainly, for decoding/encoding stuff, and calling functions dynamically.
Most of the examples were taken from projects I worked on in the past, and some from projects I am currently working on.
You will also find informative comments in the examples, that will help you to understand the code. some of them are mine, and some of them were taken from the godoc website.
If you want to contribute to this repository, don't hesitate to create a PR.
The awesome gopher in the logo was taken from @egonelbre/gophers.
Table Of Content
- Read struct tags
- Get and set struct fields
- Fill slice with values
- Set a value of a number
- Decode key-value pairs into map
- Decode key-value pairs into struct
- Encode struct into key-value pairs
- Check if the underlying type implements an interface
- Wrap a
reflect.Value
with pointer (T
=>*T
) - Function calls
Read struct tags
package main
import (
"fmt"
"reflect"
)
type User struct {
Email string `mcl:"email"`
Name string `mcl:"name"`
Age int `mcl:"age"`
Github string `mcl:"github" default:"a8m"`
}
func main() {
var u interface{} = User{}
// TypeOf returns the reflection Type that represents the dynamic type of u.
t := reflect.TypeOf(u)
// Kind returns the specific kind of this type.
if t.Kind() != reflect.Struct {
return
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(f.Tag.Get("mcl"), f.Tag.Get("default"))
}
}
Calling to Kind()
can returns one of this list.
Get and set struct fields
package main
import (
"fmt"
"reflect"
)
type User struct {
Email string `mcl:"email"`
Name string `mcl:"name"`
Age int `mcl:"age"`
Github string `mcl:"github" default:"a8m"`
}
func main() {
u := &User{Name: "Ariel Mashraki"}
// Elem returns the value that the pointer u points to.
v := reflect.ValueOf(u).Elem()
f := v.FieldByName("Github")
// make sure that this field is defined, and can be changed.
if !f.IsValid() || !f.CanSet() {
return
}
if f.Kind() != reflect.String || f.String() != "" {
return
}
f.SetString("a8m")
fmt.Printf("Github username was changed to: %q\n", u.Github)
}
Fill slice with strings, without knowing its type. Use case: decoder.
package main
import (
"fmt"
"io"
"reflect"
)
func main() {
var (
a []string
b []interface{}
c []io.Writer
)
fmt.Println(fill(&a), a) // pass
fmt.Println(fill(&b), b) // pass
fmt.Println(fill(&c), c) // fail
}
func fill(i interface{}) error {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("non-pointer %v", v.Type())
}
// get the value that the pointer v points to.
v = v.Elem()
if v.Kind() != reflect.Slice {
return fmt.Errorf("can't fill non-slice value")
}
v.Set(reflect.MakeSlice(v.Type(), 3, 3))
// validate the type of the slice. see below.
if !canAssign(v.Index(0)) {
return fmt.Errorf("can't assign string to slice elements")
}
for i, w := range []string{"foo", "bar", "baz"} {
v.Index(i).Set(reflect.ValueOf(w))
}
return nil
}
// we accept strings, or empty interfaces.
func canAssign(v reflect.Value) bool {
return v.Kind() == reflect.String || (v.Kind() == reflect.Interface && v.NumMethod() == 0)
}
Set a value of a number. Use case: decoder.
package main
import (
"fmt"
"reflect"
)
const n = 255
func main() {
var (
a int8
b int16
c uint
d float32
e string
)
fmt.Println(fill(&a), a)
fmt.Println(fill(&b), b)
fmt.Println(fill(&c), c)
fmt.Println(fill(&d), c)
fmt.Println(fill(&e), e)
}
func fill(i interface{}) error {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("non-pointer %v", v.Type())
}
// get the value that the pointer v points to.
v = v.Elem()
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.OverflowInt(n) {
return fmt.Errorf("can't assign value due to %s-overflow", v.Kind())
}
v.SetInt(n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if v.OverflowUint(n) {
return fmt.Errorf("can't assign value due to %s-overflow", v.Kind())
}
v.SetUint(n)
case reflect.Float32, reflect.Float64:
if v.OverflowFloat(n) {
return fmt.Errorf("can't assign value due to %s-overflow", v.Kind())
}
v.SetFloat(n)
default:
return fmt.Errorf("can't assign value to a non-number type")
}
return nil
}
Decode key-value pairs into map
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func main() {
var (
m0 = make(map[int]string)
m1 = make(map[int64]string)
m2 map[interface{}]string
m3 map[interface{}]interface{}
m4 map[bool]string
)
s := "1=foo,2=bar,3=baz"
fmt.Println(decodeMap(s, &m0), m0) // pass
fmt.Println(decodeMap(s, &m1), m1) // pass
fmt.Println(decodeMap(s, &m2), m2) // pass
fmt.Println(decodeMap(s, &m3), m3) // pass
fmt.Println(decodeMap(s, &m4), m4) // fail
}
func decodeMap(s string, i interface{}) error {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("non-pointer %v", v.Type())
}
// get the value that the pointer v points to.
v = v.Elem()
t := v.Type()
// allocate a new map, if v is nil. see: m2, m3, m4.
if v.IsNil() {
v.Set(reflect.MakeMap(t))
}
// assume that the input is valid.
for _, kv := range strings.Split(s, ",") {
s := strings.Split(kv, "=")
n, err := strconv.Atoi(s[0])
if err != nil {
return fmt.Errorf("failed to parse number: %v", err)
}
k, e := reflect.ValueOf(n), reflect.ValueOf(s[1])
// get the type of the key.
kt := t.Key()
if !k.Type().ConvertibleTo(kt) {
return fmt.Errorf("can't convert key to type %v", kt.Kind())
}
k = k.Convert(kt)
// get the element type.
et := t.Elem()
if et.Kind() != v.Kind() && !e.Type().ConvertibleTo(et) {
return fmt.Errorf("can't assign value to type %v", kt.Kind())
}
v.SetMapIndex(k, e.Convert(et))
}
return nil
}
Decode key-value pairs into struct
package main
import (
"fmt"
"reflect"
"strings"
)
type User struct {
Name string
Github string
private string
}
func main() {
var (
v0 User
v1 *User
v2 = new(User)
v3 struct{ Name string }
s = "Name=Ariel,Github=a8m"
)
fmt.Println(decode(s, &v0), v0) // pass
fmt.Println(decode(s, v1), v1) // fail
fmt.Println(decode(s, v2), v2) // pass
fmt.Println(decode(s, v3), v3) // fail
fmt.Println(decode(s, &v3), v3) // pass
}
func decode(s string, i interface{}) error {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr || v.IsNil() {
return fmt.Errorf("decode requires non-nil pointer")
}
// get the value that the pointer v points to.
v = v.Elem()
// assume that the input is valid.
for _, kv := range strings.Split(s, ",") {
s := strings.Split(kv, "=")
f := v.FieldByName(s[0])
// make sure that this field is defined, and can be changed.
if !f.IsValid() || !f.CanSet() {
continue
}
// assume all the fields are type string.
f.SetString(s[1])
}
return nil
}
Encode struct into key-value pairs
package main
import (
"fmt"
"reflect"
"strings"
)
type User struct {
Email string `kv:"email,omitempty"`
Name string `kv:"name,omitempty"`
Github string `kv:"github,omitempty"`
private string
}
func main() {
var (
u = User{Name: "Ariel", Github: "a8m"}
v = struct {
A, B, C string
}{
"foo",
"bar",
"baz",
}
w = &User{}
)
fmt.Println(encode(u))
fmt.Println(encode(v))
fmt.Println(encode(w))
}
// this example supports only structs, and assume their
// fields are type string.
func encode(i interface{}) (string, error) {
v := reflect.ValueOf(i)
t := v.Type()
if t.Kind() != reflect.Struct {
return "", fmt.Errorf("type %s is not supported", t.Kind())
}
var s []string
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
// Skip unexported fields. As mentioned in GoDoc:
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// For Go version >= 1.17, use the StructField.IsExported function instead.
if f.PkgPath != "" {
continue
}
fv := v.Field(i)
key, omit := readTag(f)
// skip empty values when "omitempty" set.
if omit && fv.String() == "" {
continue
}
s = append(s, fmt.Sprintf("%s=%s", key, fv.String()))
}
return strings.Join(s, ","), nil
}
func readTag(f reflect.StructField) (string, bool) {
val, ok := f.Tag.Lookup("kv")
if !ok {
return f.Name, false
}
opts := strings.Split(val, ",")
omit := false
if len(opts) == 2 {
omit = opts[1] == "omitempty"
}
return opts[0], omit
}
Check if the underlying type implements an interface
package main
import (
"fmt"
"reflect"
)
type Marshaler interface {
MarshalKV() (string, error)
}
type User struct {
Email string `kv:"email,omitempty"`
Name string `kv:"name,omitempty"`
Github string `kv:"github,omitempty"`
private string
}
func (u User) MarshalKV() (string, error) {
return fmt.Sprintf("name=%s,email=%s,github=%s", u.Name, u.Email, u.Github), nil
}
func main() {
fmt.Println(encode(User{"boring", "Ariel", "a8m", ""}))
fmt.Println(encode(&User{Github: "posener", Name: "Eyal", Email: "boring"}))
}
var marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
func encode(i interface{}) (string, error) {
t := reflect.TypeOf(i)
if !t.Implements(marshalerType) {
return "", fmt.Errorf("encode only supports structs that implement the Marshaler interface")
}
m, _ := reflect.ValueOf(i).Interface().(Marshaler)
return m.MarshalKV()
}
reflect.Value
with pointer (T
=> *T
)
Wrap a package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
}
func main() {
// T => *T
u1 := User{"a8m"}
p1 := ptr(reflect.ValueOf(u1))
fmt.Println(u1 == p1.Elem().Interface())
// *T => **T
u2 := &User{"a8m"}
p2 := ptr(reflect.ValueOf(u2))
fmt.Println(*u2 == p2.Elem().Elem().Interface())
}
// ptr wraps the given value with pointer: V => *V, *V => **V, etc.
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
}
Function calls
Call method without prameters, and without return value
package main
import (
"fmt"
"reflect"
)
type A struct{}
func (A) Hello() { fmt.Println("World") }
func main() {
// ValueOf returns a new Value, which is the reflection interface to a Go value.
v := reflect.ValueOf(A{})
m := v.MethodByName("Hello")
if m.Kind() != reflect.Func {
return
}
m.Call(nil)
}
Call function with list of arguments, and validate return values
package main
import (
"fmt"
"reflect"
)
func Add(a, b int) int { return a + b }
func main() {
v := reflect.ValueOf(Add)
if v.Kind() != reflect.Func {
return
}
t := v.Type()
argv := make([]reflect.Value, t.NumIn())
for i := range argv {
// validate the type of parameter "i".
if t.In(i).Kind() != reflect.Int {
return
}
argv[i] = reflect.ValueOf(i)
}
// note that, len(result) == t.NumOut()
result := v.Call(argv)
if len(result) != 1 || result[0].Kind() != reflect.Int {
return
}
fmt.Println(result[0].Int())
}
Call to a function dynamically. similar to the template/text package
package main
import (
"fmt"
"html/template"
"reflect"
"strconv"
"strings"
)
func main() {
funcs := template.FuncMap{
"trim": strings.Trim,
"lower": strings.ToLower,
"repeat": strings.Repeat,
"replace": strings.Replace,
}
fmt.Println(eval(`{{ "hello" 4 | repeat }}`, funcs))
fmt.Println(eval(`{{ "Hello-World" | lower }}`, funcs))
fmt.Println(eval(`{{ "foobarfoo" "foo" "bar" -1 | replace }}`, funcs))
fmt.Println(eval(`{{ "-^-Hello-^-" "-^" | trim }}`, funcs))
}
// evaluate an expression. note that this implemetation is assuming that the
// input is valid, and also very limited. for example, whitespaces are not allowed
// inside a quoted string.
func eval(s string, funcs template.FuncMap) (string, error) {
args, name := parseArgs(s)
fn, ok := funcs[name]
if !ok {
return "", fmt.Errorf("function %s is not defined", name)
}
v := reflect.ValueOf(fn)
t := v.Type()
if len(args) != t.NumIn() {
return "", fmt.Errorf("invalid number of arguments. got: %v, want: %v", len(args), t.NumIn())
}
argv := make([]reflect.Value, len(args))
// go over the arguments, validate and build them.
// note that we support only int, and string in this simple example.
for i := range argv {
var argType reflect.Kind
// if the argument "i" is string.
if strings.HasPrefix(args[i], "\"") {
argType = reflect.String
argv[i] = reflect.ValueOf(strings.Trim(args[i], "\""))
} else {
argType = reflect.Int
// assume that the input is valid.
num, _ := strconv.Atoi(args[i])
argv[i] = reflect.ValueOf(num)
}
if t.In(i).Kind() != argType {
return "", fmt.Errorf("Invalid argument. got: %v, want: %v", argType, t.In(i).Kind())
}
}
result := v.Call(argv)
// in real-world code, we validate it before executing the function,
// using the v.NumOut() method. similiar to the text/template package.
if len(result) != 1 || result[0].Kind() != reflect.String {
return "", fmt.Errorf("function %s must return a one string value", name)
}
return result[0].String(), nil
}
// parseArgs is an auxiliary function, that extract the function and its
// parameter from the given expression.
func parseArgs(s string) ([]string, string) {
args := strings.Split(strings.Trim(s, "{ }"), "|")
return strings.Split(strings.Trim(args[0], " "), " "), strings.Trim(args[len(args)-1], " ")
}
Call function with variadic parameter
package main
import (
"fmt"
"math/rand"
"reflect"
)
func Sum(x1, x2 int, xs ...int) int {
sum := x1 + x2
for _, xi := range xs {
sum += xi
}
return sum
}
func main() {
v := reflect.ValueOf(Sum)
if v.Kind() != reflect.Func {
return
}
t := v.Type()
argc := t.NumIn()
if t.IsVariadic() {
argc += rand.Intn(10)
}
argv := make([]reflect.Value, argc)
for i := range argv {
argv[i] = reflect.ValueOf(i)
}
result := v.Call(argv)
fmt.Println(result[0].Int()) // assume that t.NumOut() > 0 tested above.
}
Create function at runtime
package main
import (
"fmt"
"reflect"
)
type Add func(int64, int64) int64
func main() {
t := reflect.TypeOf(Add(nil))
mul := reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value {
a := args[0].Int()
b := args[1].Int()
return []reflect.Value{reflect.ValueOf(a+b)}
})
fn, ok := mul.Interface().(Add)
if !ok {
return
}
fmt.Println(fn(2,3))
}