• Stars
    star
    555
  • Rank 80,213 (Top 2 %)
  • Language
  • Created almost 7 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Bunch of examples for dealing with the reflect package

Golang reflect package examples

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

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()
}

Wrap a reflect.Value with pointer (T => *T)

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
}

Playground

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))
}

More Repositories

1

golang-cheat-sheet

An overview of Go syntax and features.
8,366
star
2

angular-filter

Bunch of useful filters for AngularJS (with no external dependencies!)
JavaScript
2,927
star
3

envsubst

Environment variables substitution for Go
Go
732
star
4

djson

Fast Go decoder for dynamic JSON
Go
601
star
5

pb

Console progress bar for Rust
Rust
580
star
6

rql

Resource Query Language for REST
Go
337
star
7

syncmap

A typed implementation of the Go sync.Map using code generation
Go
256
star
8

mark

A markdown processor written in Go. built for fun.
Go
203
star
9

play

Play something while waiting for your command to finish
Go
185
star
10

kinesis-producer

An aggregated records producer for Amazon Kinesis
Go
147
star
11

enter

A CLI for generating ER diagrams for Ent schema
Go
131
star
12

ng-pipes

Bunch of useful pipes for Angular2 (with no external dependencies!)
TypeScript
118
star
13

tree

An implementation of the Unix tree command written in Go, that can be used programmatically
Go
92
star
14

agile

Like Underscore, but with zero callbacks and really more fun, v0.0.2
JavaScript
69
star
15

ent-graphql-example

The code for https://entgo.io/docs/tutorial-setup
Go
60
star
16

doqmentdb

A Promise-Based DocumentDB ODM Client for NodeJS
JavaScript
52
star
17

ng-translation

Fast, Easy and Dynamic translation for AngularJS
JavaScript
43
star
18

pb-scala

Console progress bar for Scala
Scala
37
star
19

documentdb

Go driver for Microsoft Azure DocumentDB
Go
33
star
20

expect

Minimalistic BDD-style assertions for Go (inspired by expect.js)
Go
32
star
21

deep-keys

Create an array composed of the own enumerable property names (including nested) of an object.
JavaScript
23
star
22

lease

Generic lease implementation using DynamoDB
Go
20
star
23

angular-code-mirror

2 way binding codemirror for AngularJS based on google-prettify
JavaScript
13
star
24

entclean

Clean ent/schemas
Go
11
star
25

errors

An experimental error handling package for Go
Go
10
star
26

clog

Pretty colorful cli logger for NodeJS(with table, success and more...)
JavaScript
9
star
27

s3tree

s3tree is a tree command for Amazon S3
Go
7
star
28

entspatial

An example repository for working with MySQL spatial data types in ent
Go
7
star
29

gotips-talk-2018

"Did you know that..." talk. Go-Israel meetup, Jan 2018
Go
6
star
30

maman14

maman14 - assembler
C
5
star
31

go-documentdb-example

A users CRUD app using Martini and DocumentDB
Go
4
star
32

obj-parse

Get and Set object properties in a Fast and Elegant way. (with caching and no dependencies!)
JavaScript
4
star
33

obj-del

Remove multiple keys by path - safety.
JavaScript
3
star
34

entsize

Print ent/schema size
Go
3
star
35

flag.js

cli flag parsing
JavaScript
2
star
36

dynamose

A Promise-Based DynamoDB Client
JavaScript
2
star
37

ent-sync-example

The code for https://entgo.io/blog/2021/11/1/sync-objects-in-external-database
Go
2
star
38

stringify.js

like JSON.stringify, but more sense
JavaScript
1
star
39

mark-cli

Mark command-line tool
Go
1
star
40

koa-documentdb-example

A users CRUD app using Koa and DoQmentDB(DocumentDB wrapper)
JavaScript
1
star
41

obj-is

is-function's creator
JavaScript
1
star
42

entraffle

A raffle for Ent Discord members
Go
1
star