Essential Go Reflection  Suggest an edit

Structs

List fields of a struct

Using reflection we can list all fields of a struct.

type S struct {
	FirstName string `my_tag:"first-name"`
	lastName  string
	Age       int `json:"age",xml:"AgeXml`
}

func describeStructSimple(rv reflect.Value) {
	structType := rv.Type()
	for i := 0; i < rv.NumField(); i++ {
		v := rv.Field(i)
		structField := structType.Field(i)
		name := structField.Name
		typ := structField.Type
		tag := structField.Tag
		jsonTag := tag.Get("json")
		isExported := structField.PkgPath == ""
		if isExported {
			fmt.Printf("name: '%s',\ttype: '%s', value: %v,\ttag: '%s',\tjson tag: '%s'\n", name, typ, v.Interface(), tag, jsonTag)
		} else {
			fmt.Printf("name: '%s',\ttype: '%s',\tvalue: not accessible\n", name, v.Type().Name())
		}
	}
}

func main() {
	s := S{
		FirstName: "John",
		lastName:  "Doe",
		Age:       27,
	}
	describeStructSimple(reflect.ValueOf(s))
}
name: 'FirstName',	type: 'string', value: John,	tag: 'my_tag:"first-name"',	json tag: ''
name: 'lastName',	type: 'string',	value: not accessible
name: 'Age',	type: 'int', value: 27,	tag: 'json:"age",xml:"AgeXml',	json tag: 'age'

Using reflection we can only access values (v.Interface{}) of exported fields.

Exported fields are fields with names starting with upper case (FirstName and Age are exported, lastName is not).

Field is exported if reflect.StructField.PkgPath == "".

List fields of a struct recursively

Inspecting a struct is inherently recursive process.

You have to chase pointers and recurse into embedded structures.

In real programs inspecting structures using reflections would be recursive.

type Inner struct {
	N int
}

type S struct {
	Inner
	NamedInner Inner
	PtrInner   *Inner
	unexported int
	N          int8
}

func indentStr(level int) string {
	return strings.Repeat("  ", level)
}

// if sf is not nil, this is a field of a struct
func describeStruct(level int, rv reflect.Value, sf *reflect.StructField) {
	structType := rv.Type()
	nFields := rv.NumField()
	typ := rv.Type()
	if sf == nil {
		fmt.Printf("%sstruct %s, %d field(s), size: %d bytes\n", indentStr(level), structType.Name(), nFields, typ.Size())
	} else {
		fmt.Printf("%sname: '%s' type: 'struct %s', offset: %d, %d field(s), size: %d bytes, embedded: %v\n", indentStr(level), sf.Name, structType.Name(), sf.Offset, nFields, typ.Size(), sf.Anonymous)
	}

	for i := 0; i < nFields; i++ {
		fv := rv.Field(i)
		sf := structType.Field(i)
		describeType(level+1, fv, &sf)
	}
}

// if sf is not nil, this is a field of a struct
func describeType(level int, rv reflect.Value, sf *reflect.StructField) {
	switch rv.Kind() {

	case reflect.Int, reflect.Int8:
		// in real code we would handle more primitive types
		i := rv.Int()
		typ := rv.Type()
		if sf == nil {
			fmt.Printf("%stype: '%s', value: '%d'\n", indentStr(level), typ.Name(), i)
		} else {
			fmt.Printf("%s name: '%s' type: '%s', value: '%d', offset: %d, size: %d\n", indentStr(level), sf.Name, typ.Name(), i, sf.Offset, typ.Size())
		}

	case reflect.Ptr:
		fmt.Printf("%spointer\n", indentStr(level))
		describeType(level+1, rv.Elem(), nil)

	case reflect.Struct:
		describeStruct(level, rv, sf)
	}
}

func main() {
	var s S
	describeType(0, reflect.ValueOf(s), nil)
}
struct S, 5 field(s), size: 40 bytes
  name: 'Inner' type: 'struct Inner', offset: 0, 1 field(s), size: 8 bytes, embedded: true
     name: 'N' type: 'int', value: '0', offset: 0, size: 8
  name: 'NamedInner' type: 'struct Inner', offset: 8, 1 field(s), size: 8 bytes, embedded: false
     name: 'N' type: 'int', value: '0', offset: 0, size: 8
  pointer
   name: 'unexported' type: 'int', value: '0', offset: 24, size: 8
   name: 'N' type: 'int8', value: '0', offset: 32, size: 1

We can access not only

  ↑ ↓ to navigate     ↵ to select     Esc to close