Golang reflection - Part 2

This article is Golang reflection - Part 1 It mainly introduces some practical uses of reflection

1. Judge type interface.Type

The usage of using type assertions to determine data types is as follows

package main

import "fmt"

func main()  {
	var s interface{} = "abc"
	switch s.(type) {
	case string:
		fmt.Println("s.type=string")
	case int:
		fmt.Println("s.type=int")
	case bool:
		fmt.Println("s.type=bool")
	default:
		fmt.Println("Unknown type")
	}
}

Problems in judging the above types

  • Type judgment will write a lot, and the code is very long
  • Types can also be added or deleted, which is not flexible

If you use reflection to get information inside a variable

  • The reflect package provides ValueOf and TypeOf
  • reflect.ValueOf: get the value of the data in the input interface. If it is empty, return 0
  • reflect.TypeOf: gets the type of value in the input interface. If it is empty, it returns nil
  • TypeOf can pass in all types because all types implement empty interfaces
package main

import (
	"fmt"
	"reflect"
)

func main()  {
	var s interface{} = "abc"
	//TypeOf returns the target object
	reflectType:=reflect.TypeOf(s)
	reflectValue:=reflect.ValueOf(s)
	fmt.Printf("[typeof:%v]\n", reflectType)  // string
	fmt.Printf("[valueof:%v]\n", reflectValue)  // abc
}

2. Reflection of custom struct

User defined struct related operations

  • For member variables
    • First get the reflect.Type of the interface, and then traverse NumField
    • Then get the Field name and type through the Field of reflect.Type
    • Finally, obtain the corresponding value through the Field interface
  • For method
    • First get the reflect.Type of the interface, and then traverse the NumMethod
    • Then get the real method name through t.Method of reflect.Type
    • Finally, get the Type and value of the method through Name and Type

Attention

  • Used to traverse an unknown type, probe its Field, and abstract it into a function
  • In go language, struct member variables are lowercase, and panic() is used directly when reflecting
  • The structure method name is lowercase, and the reflection value will not be viewed
  • Pointer methods cannot be reflected
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age  int
}

type Student struct {
	Person     // Anonymous structure nesting
	StudentId  int
	SchoolName string
	Graduated  bool
	Hobbies    []string
	//panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
	//hobbies    []string
	Label      map[string]string
}

func (s *Student) GoHome() {
	fmt.Printf("go back home,sid:%d\n", s.StudentId)
}

//func (s Student) GoHome() {
//	fmt.Printf("home, sid:%d\n", s.StudentId)
//}

func (s Student) GotoSchool() {
	fmt.Printf("School,sid:%d\n", s.StudentId)
}

func (s *Student) graduated() {
	fmt.Printf("Graduated,sid:%d\n", s.StudentId)
}

//func (s Student) Ggraduated() {
//	fmt.Printf("graduated, sid:%d\n", s.StudentId)
//}

func reflectProbeStruct(s interface{}) {
	// Get target object
	t := reflect.TypeOf(s)
	fmt.Printf("The type name of the object %s\n", t.Name())
	// Gets the value type of the target object
	v := reflect.ValueOf(s)
	// Traversal to get member variables
	for i := 0; i < t.NumField(); i++ {
		// Field represents the field name of the object
		key := t.Field(i)
		value := v.Field(i).Interface()
		// field
		if key.Anonymous {
			fmt.Printf("Anonymous field No %d Field name %s, Field type %v, The value of the field %v\n", i+1, key.Name, key.Type, value)
		} else {
			fmt.Printf("Named field No %d Field name %s, Field type %v, The value of the field %v\n", i+1, key.Name, key.Type, value)
		}
	}
	// Printing method
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Printf("The first %d Method name %s, Method type %v\n", i+1, m.Name, m.Type)
	}
}

func main() {
	s := Student{
		Person: Person{
			"geek",
			24,
		},
		StudentId:  123,
		SchoolName: "Beijing University",
		Graduated:  true,
		Hobbies:    []string{"sing", "jump", "Rap"},
		//Hobbies: [] string {"sing", "jump", "Rap"},
		Label:      map[string]string{"k1": "v1", "k2": "v2"},
	}
	p := Person{
		Name: "Zhang San",
		Age:  100,
	}
	reflectProbeStruct(s)
	reflectProbeStruct(p)
	/*
	Object's type name Student
	Anonymous field the first field, field name Person, field type main.Person, field value {geek 24}
	Name the second field, field name StudentId, field type int, field value 123
	Name the third field, the field name is SchoolName, the field type is string, and the field value is Beijing University
	Name the fourth field of the field, the field name is graded, the field type is bool, and the value of the field is true
	Name the fifth field, field name Hobbies, field type [] string, field value [Rap]
	Name the sixth field of the field, field name Label, field type map[string]string, field value map [K1: V1, K2: V2]
	The first method, method name GotoSchool, method type func(main.Student)
	The type name of the object is Person
	Name the first field, field name name, field type string, and field value Zhang San
	Name the second field, field name Age, field type int, field value 100
	 */
}

3. Structure labels and Reflections

  • json tag parses json
  • The label of yaml parses yaml
  • The tags of xorm and gorm identify the database db field
  • Custom label
  • The principle is t.Field.Tag.Lookup("tag name")

Example

package main

import (
	"encoding/json"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
)

type Person struct {
	Name string `json:"name" yaml:"yaml_name"`
	Age  int    `json:"age" yaml:"yaml_age"`
	City string `json:"city" yaml:"yaml_city"`
	//City string `json:"-" yaml:"yaml_city" ` / / ignore json: "-"
}

// json parsing
func jsonWork() {
	// Marshal object into string
	p := Person{
		Name: "geek",
		Age:  24,
		City: "Beijing",
	}
	data, err := json.Marshal(p)
	if err != nil {
		fmt.Printf("json.marshal.err: %v\n", err)
	}
	fmt.Printf("person.marshal.res: %v\n", string(data))

	// Parse from string to struct
	p2str := `{
	"name": "Zhang San",
	"age": 38,
	"city": "Shandong"
	}`
	var p2 Person
	err = json.Unmarshal([]byte(p2str), &p2)
	if err != nil {
		fmt.Printf("json.unmarshal.err: %v\n", err)
		return
	}
	fmt.Printf("person.unmarshal.res: %v\n", p2)
}

// yaml parsing
func yamlWork() {
	filename := "a.yaml"
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("ioutil.ReadFile.err: %v\n", err)
		return
	}
	p := &Person{}
	//err = yaml.Unmarshal([]byte(content), p)
	err = yaml.UnmarshalStrict([]byte(content), p)  // Strict parsing, consider redundant fields, ignore fields, etc
	if err != nil {
		fmt.Printf("yaml.UnmarshalStrict.err: %v\n", err)
		return
	}
	fmt.Printf("yaml.UnmarshalStrict.res: %v\n", p)
}

func main() {
	jsonWork()
	/*
		person.marshal.res: {"name":"geek","age":24,"city":"Beijing"}
		person.unmarshal.res: {Zhang San 38 Shandong}
	*/
	yamlWork()
	/*
		yaml.UnmarshalStrict.res: &{Li Si 18 Shanghai}
	 */
}

Parsed yaml content

yaml_name: Li Si
yaml_age: 18
yaml_city: Shanghai
  • Custom label format resolution
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string `aa:"name"`
	Age  int    `aa:"age"`
	City string `aa:"city"`
}

// CustomParse custom parsing
func CustomParse(s interface{}) {
	// TypeOf type
	r:=reflect.TypeOf(s)
	value := reflect.ValueOf(s)
	for i:=0;i<r.NumField();i++{
		field:=r.Field(i)
		key:=field.Name
		if tag, ok:=field.Tag.Lookup("aa");ok{
			if tag == "-"{
				continue
			}
			fmt.Printf("eureka aa label, key: %v, value: %v, tag: %s\n", key, value.Field(i), tag)
		}
	}
}

func main() {
	p := Person{
		Name: "geek",
		Age:  24,
		City: "Beijing",
	}
	CustomParse(p)
	/*
	aa tag found, key: Name, value: geek, tag: name
	aa tag found, key: Age, value: 24, tag: age
	aa tag found, key: City, value: Beijing, tag: city
	 */
}

4. Reflection calling function

valueFunc := reflect.ValueOf(Add) //Function is also a data type
typeFunc := reflect.TypeOf(Add)
argNum := typeFunc.NumIn()            //Number of function input parameters
args := make([]reflect.Value, argNum) //Prepare the input parameters of the function
for i := 0; i < argNum; i++ {
	if typeFunc.In(i).Kind() == reflect.Int {
		args[i] = reflect.ValueOf(3) //Assign 3 to each parameter
	}
}
sumValue := valueFunc.Call(args) //Return [] reflect.Value, because the function of go language may return a list
if typeFunc.Out(0).Kind() == reflect.Int {
	sum := sumValue[0].Interface().(int) //Convert from Value to raw data type
	fmt.Printf("sum=%d\n", sum)
}

5. Reflection call method

Example

user := User{
	Id:     7,
	Name:   "Jackson",
	Weight: 65.5,
	Height: 1.68,
}
valueUser := reflect.ValueOf(&user)              //You must pass a pointer, because BMI() is a pointer method when it is defined
bmiMethod := valueUser.MethodByName("BMI")       //MethodByName() returns the member variable of the class through Name
resultValue := bmiMethod.Call([]reflect.Value{}) //Pass an empty slice when there are no parameters
result := resultValue[0].Interface().(float32)
fmt.Printf("bmi=%.2f\n", result)

//Think() does not use a pointer when defining. valueUser can use a pointer or not
thinkMethod := valueUser.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

valueUser2 := reflect.ValueOf(user)
thinkMethod = valueUser2.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

process

  • First, get the reflection type object through reflect.ValueOf(p1)
  • reflect.ValueOf(p1).MethodByName needs to pass in an accurate method name (incorrect name: panic: reflect: call of reflect.Value.Call on zero Value), and MethodByName represents registration
  • [] reflect.Value this is the parameter of the method that needs to be called finally. Pass null slice without parameter
  • Call call
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name   string
	Age    int
	Gender string
}

func (p Person) ReflectCallFuncWithArgs(name string, age int) {
	fmt.Printf("A method with parameters is called, args.name: %s, args.age: %d, p.name: %s, p.age: %d\n",
		name,
		age,
		p.Name,
		p.Age,
	)
}

func (p Person) ReflectCallFuncWithNoArgs() {
	fmt.Printf("A method without parameters is called\n")
}

func main() {
	p1 := Person{
		Name:   "geek",
		Age:    24,
		Gender: "male",
	}
	// 1. First, get the reflection value type through reflect.ValueOf(p1)
	getValue := reflect.ValueOf(p1)
	// 2. Method call with parameters
	methodValue1 := getValue.MethodByName("ReflectCallFuncWithArgs")
	// The parameter is the slice of reflect.Value
	args1 := []reflect.Value{reflect.ValueOf("Zhang San"), reflect.ValueOf(30)}
	methodValue1.Call(args1)
	// 3. Method call without parameters
	methodValue2 := getValue.MethodByName("ReflectCallFuncWithNoArgs")
	// The parameter is the slice of reflect.Value
	args2 := make([]reflect.Value, 0)
	methodValue2.Call(args2)
	/*
	The method with parameters is called, args.name: Zhang San, args.age: 30, p.name: geek, p.age: 24
	A method without parameters is called
	 */
}

6. Reflection creation value

6.1 create struct by reflection

t := reflect.TypeOf(User{})
value := reflect.New(t) //Create an object according to reflect.Type, get the pointer of the object, and then refer to reflect.Value according to the pointer
value.Elem().FieldByName("Id").SetInt(10)
value.Elem().FieldByName("Name").SetString("Song Jiang")
value.Elem().FieldByName("Weight").SetFloat(78.)
value.Elem().FieldByName("Height").SetFloat(168.4)
user := value.Interface().(*User) //Convert reflection type to go raw data type
fmt.Printf("id=%d name=%s weight=%.1f height=%.1f\n", user.Id, user.Name, user.Weight, user.Height)

6.2 create slice for reflection

var slice []User
sliceType := reflect.TypeOf(slice)
sliceValue := reflect.MakeSlice(sliceType, 1, 3) //reflect.MakeMap,reflect.MakeSlice,reflect.MakeChan,reflect.MakeFunc
sliceValue.Index(0).Set(reflect.ValueOf(User{
	Id:     8,
	Name:   "Li Da",
	Weight: 80,
	Height: 180,
}))
users := sliceValue.Interface().([]User)
fmt.Printf("1st user name %s\n", users[0].Name)

6.3 create map for reflection

var userMap map[int]*User
mapType := reflect.TypeOf(userMap)
// mapValue:=reflect.MakeMap(mapType)
mapValue := reflect.MakeMapWithSize(mapType, 10) //reflect.MakeMap,reflect.MakeSlice,reflect.MakeChan,reflect.MakeFunc

user := &common.User{
	Id:     7,
	Name:   "Jackson",
	Weight: 65.5,
	Height: 1.68,
}
key := reflect.ValueOf(user.Id)
mapValue.SetMapIndex(key, reflect.ValueOf(user))                    //SetMapIndex adds a key value pair to the map
mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("Linghu's knife") //MapIndex retrieves the corresponding map according to the Key
userMap = mapValue.Interface().(map[int]*User)
fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)

7. Reflection modification value

Reflection modification value must be of pointer type

Operation of modifying value: pointer.Elem().Setxxx()

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 3.14
	fmt.Printf("Original value %f\n", num)
	// Get the value in num through reflect.ValueOf. You must be a pointer to modify the value
	//Pointer: = reflect. Valueof (Num) / / passing values directly will panic
	pointer := reflect.ValueOf(&num)
	newValue := pointer.Elem()
	// Assign a new value
	newValue.SetFloat(5.66)
	fmt.Printf("New value %f\n", num)
}

7.1 reflection modification struct

user := User{
	Id:     7,
	Name:   "Jackson",
	Weight: 65.5,
	Height: 1.68,
}
valueUser := reflect.ValueOf(&user)
// valueS.Elem().SetInt(8) / / panic
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName() returns the member variable of the class through Name. FieldByName cannot be called on pointer Value
addrValue := valueUser.Elem().FieldByName("addr")
if addrValue.CanSet() {
	addrValue.SetString("Beijing")
} else {
	fmt.Println("addr It is an unexported member and cannot be exported Set") //Members beginning with lowercase letters are private members
}

7.2 modifying slice by reflection

The following example indirectly implements the append function

users := make([]*User, 1, 5) //len=1,cap=5

sliceValue := reflect.ValueOf(&users) //Users are going to be modified through Value, so the address of users is passed
if sliceValue.Elem().Len() > 0 {      //Get the length of slice
	sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("Ha ha ha")
	// u0 := users[0]
	fmt.Printf("1st user name change to %s\n", users[0].Name)
}

sliceValue.Elem().SetCap(3) //The new cap must be between the original len and cap
sliceValue.Elem().SetLen(2)
//Call the Set() function of reflect.Value to modify the original data pointed to by its underlying layer
sliceValue.Elem().Index(1).Set(reflect.ValueOf(&User{
	Id:     8,
	Name:   "geek",
	Weight: 80,
	Height: 180,
}))
fmt.Printf("2nd user name %s\n", users[1].Name)

7.3 reflection modification map

u1 := &User{
	Id:     7,
	Name:   "Jackson",
	Weight: 65.5,
	Height: 1.68,
}
u2 := &User{
	Id:     8,
	Name:   "Jackson",
	Weight: 65.5,
	Height: 1.68,
}
userMap := make(map[int]*User, 5)
userMap[u1.Id] = u1

mapValue := reflect.ValueOf(&userMap)                                                         //Prepare to modify userMap through Value, so send the address of userMap
mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2))                      //SetMapIndex adds a key value pair to the map
mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("Linghu's knife") //MapIndex retrieves the corresponding map according to the Key
for k, user := range userMap {
	fmt.Printf("key %d name %s\n", k, user.Name)
}

See you ~

Posted on Tue, 23 Nov 2021 21:46:50 -0500 by Rincewind