During development, there will be many conversions between strings and structures. Especially when calling the API, the JSON string returned by the API needs to be converted into a struct structure for easy operation. So how is a JSON string converted to a struct structure? This requires the knowledge of reflection.
What is reflection?
Like the Java language, the go language also has runtime reflection, which provides us with the ability to manipulate any type of object at runtime. For example, check the specific type of an interface variable, check the number of fields in the structure, and modify the value of a field. Go language is a statically compiled language. For example, if you define a variable, you already know what type it is. Why do you need reflection? Because some things can only be known at runtime. For example, a function is defined with a parameter of * * interface {} * *, which means that the caller can pass any type of parameter to the function. In this case, if you want to know what type of parameter the caller passes, you need to use reflection. If you want to know which fields and methods a structure has, you also need to use reflection.
func PrintLn(a ...interface{}) (n int, err error){} // fmt. There are variable parameters in the println () source code. The type is interface {}, which means that zero or more values of any type can be passed to it, and they cannot be printed.
reflect.Value and reflect Type
In the Go language reflection definition, any interface consists of two parts: the specific type of the interface and the value corresponding to the specific type.
For example, var i int =3, which means that interface {} can represent any Type, so the variable i can be converted to interface {}. A variable can be regarded as an interface, and the representation of this variable in the Go reflection is < Value, Type >. Where Value is the Value of the variable and Type is the Type of the variable.
Tip: interface {} is an empty interface. It can represent any type, that is, it can convert any type into an interface. It is usually used for reflection and type assertion to reduce repeated code and simplify programming.
In Go reflection, the standard library provides us with two types of reflect Value and reflect Type to represent the value and type of the variable respectively, and provides two functions, reflect Valueof and reflect Type to get the reflection of any object respectively Value and reflect Type.
func main(){ i := 3 iv := reflect.ValueOf(i) it := reflect.Typeof(i) fmt.Println(iv, it) // 3 int } // Defines an i of type int with a value of 3.
reflect.Value
reflect.Value can be through reflect Valueof function. Its structure is defined as follows:
type Value struct { typ *rtype ptr unsafe.Pointer flag } // It is not difficult to find that the structure fields are private, that is, we can only use reflect Value method. Its common methods are as follows: // Series methods for specific types // The following is used to obtain the corresponding value Bool Bytes Complex Float Int String Uint Canset //Can I modify the corresponding value // The following values are used to modify the corresponding values Set SetBool SetBytes SetComplex SetFloat SetInt SetString Elem // Get the value pointed to by the pointer, which is generally used to modify the corresponding value // The following Field series methods are used to get fields in struct types Field FieldByIndex FieldByName FieldByNameFunc Interface // Get the corresponding original type IsNil // Is the value nil IsZero // Is the value zero Kind // Get the corresponding types, such as Array, Slice, Map, etc // Get the corresponding method Method MethodName NumField // Gets the number of fields in the struct type NumMethod // Number of method sets on type Type // Get the corresponding reflect Type // In fact, there are three types: one is used to obtain and modify the corresponding value; One is related to the field of struct type, which is used to obtain the corresponding field; A class is related to the method set on the type and is used to obtain the corresponding method.
Get original type
func main(){ i := 3 // int to reflect.Value iv := reflect.ValueOf(i) // reflect.Value to int i1 := iv.Interface().(int) fmt.Println(i1) } // This is reflect Value and int types can be converted to each other, and other types can also be replaced.
Modify the corresponding value
func main(){ i := 3 ipv := reflect.ValueOf(&i) ipv.Elem().SetInt(4) fmt.Println(i) } // This modifies a variable through reflection. Because reflect The valueof function can return a copy, so how can I pass in the pointer of the variable. Because of the passed value pointer, you need to call the Elem method to find the variable pointed to by the pointer, so as to modify it.
To modify the value of a variable, there are several key points: pass the pointer (addressable), and obtain the pointed value through the Elem method to ensure that the value can be modified. Reflect Value provides us with CanSet method to judge whether the variable can be modified. So how to modify the value of a struct structure field? The following steps can be summarized from the modification of reference variables:
- Pass a pointer to the struct structure and get the corresponding reflec Value
- Get the value pointed to by the pointer through the Elem method
- Get the Field to be modified through the Field method
- Modify to the corresponding value through the Set method
func main() { p := person{name:"zhangsan", age:18} ppv := reflect.ValueOf(&p) ppv.Elem().Field(0).SetString("lisi") fmt.Println(p) } type person struct{ Name string Age int }
Summary: Rules for modifying a value through reflection. Remember these rules, you can modify the value of a variable or field through reflection when the program is running:
1. Addressable, colloquially speaking, is to reflect.ValueOf Function passes a pointer as an argument 2. If you want to modify struct If the structure field value is, the field must be exportable rather than private, that is, the field must be capitalized 3. Remember to use Elem Method gets the value pointed to by the pointer so that it can be called Set A series of methods are modified.
Get the corresponding underlying type
What does the underlying type mean? In fact, it mainly corresponds to basic types, such as interface, structure and pointer... Because we can declare many new types through the type keyword. In the above example, p person is a new type. The underlying type corresponding to person is struct, and &p corresponds to pointer type.
func main(){ p := person{Name:"zhangsan", Age:18} ppv := reflect.ValueOf(&p) fmt.Println(vvp.Kind()) pv := reflect.ValueOf(p) fmt.Println(pv.Kind()) } // Output results ptr struct // The Kind method returns a Kind type value, which is a constant with the following available values type Kind uint const{ Invalid Kin = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintprt Float32 Float64 Complex64 Complex128 Array Chan Func Interfact Map Ptr Slcie Struct UnsafePointer } // From the list of Kind constants defined in the source code, all the underlying types of Go language have been included
reflect.Type
reflect.Value can be used to summarize operations related to value, and if it is an operation related to variable type, it is best to use reflect Type, such as the field name or method corresponding to the structure. To get a variable's reflect Type, you can use reflect TypeOf. And reflect Different values, reflect Type is an interface, not a structure, so you can only use its methods. Interface definition:
type Type interface{ Implements(u Type) bool AssignableTo(u Type) bool CovertibleTo(u Type) bool Comparable() bool // The following methods have the same function as the Value structure Kind() Kind Method(int) Method MethodByName(string) (Method, bool) NumMethod int Elem() Type Field(i int) StructField FieldByIndex(index []int) StructField FieldByName(name string) (StructField, bool) FieldByNameFunc(match func(string) bool) (StructField, bool) NumField() int }
Several specific methods are as follows:
1. Implements Method is used to determine whether the interface is implemented u 2. AssignableTo Method is used to determine whether a value can be assigned to a type u,In fact, it is whether it can be used=, Assignment operator 3. ConvertibleTo Method is used to determine whether it can be converted to a type u,In fact, it is whether type conversion can be performed 4. Comparable Method is used to determine whether the type is comparable. In fact, it is whether relational operators can be used for comparison
Fields and methods for traversing structures
Add a String method to the person structure in the above example:
func (p person) String() string{ return fmt.Sprintf("Name is %s, Age is %d", p.Name, p.Age) } // Add a new String method to return the corresponding String information, so that the person structure also implements FMT Stringer interface // The NumField method is used to obtain the number of structure fields, and the for loop is used to traverse the structure fields. Similarly, the NumMethod method is used to obtain the number of structure methods func main() { p := person{Name:"Running snail ", Age:18} pt : = reflect.TypeOf(p) // Traverse the person field for i:=0; i< pt.NumFiled(); i++{ fmt.Println("field", pt.Field(i).Name) } // Traversal person method for i := 0; i < NumMethod(); i++ { fmt.Println("method", pt.Method(i).Name) } }
Tip: get the specified field through the FieldByName method or the specified method through the MethodName method. This is very efficient when you need to get a specified field or method, rather than traversal
Whether to implement an interface
adopt reflect.Type You can also determine whether an interface is implemented. Or with person Structure as an example to judge whether the interface is implemented fmt.Stringer and io.Writer ,As follows:
func main(){ p := person{Name:"Running snail ", Age:18} pt := reflect.TypeOf(p) stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() writerType := reflect.TypeOf((*io.Writer)(nil)).Elem() fmt.Println("Is it implemented fmt.Stringer", pt.Implements(stringerType)) fmt.Println("Is it implemented io.Writer", pt.Impements(writerType)) } // Output results Is it implemented fmt.Stringer true Is it implemented io.Writer false
Tip: try to judge whether an interface is implemented by type assertion rather than reflection.
String and struct to each other
In the scenario of string and structure conversion, JSON and struct conversion are the most used.
JSON and Struct turn to each other
The standard library of Go language has a JSON package, which can convert a JSON string into a struct structure or a struct structure into a JSON string.
func main() { P := person{Name:"Running snail ", Age :18} // struct to json jsonA, err := json.Marshal(p) if err != nil { fmt.Println(string(jsonA)) } // json to struct respJson := "{\"Name\":\"One armed Astro Boy\",\"Age\":19}" json.Unmarshal([]byte(respJson), &p) fmt.Println(p) } // Output results {"Name":"Running snail ", "Age":18} Name is One armed Astro Boy,Age is 19 // This example is demonstrated through the JSON standard package provided by the Go language. Through JSON Marshal function, which can convert a struct into a JSON string. Through JSON The unmarshal function can convert a JSON string to struct
Struct Tag
In the above example, the key of JSON string is the same as the field name of struct structure, so can they be changed? To achieve this goal, you need to use the function of * * struct tag * *. As the name suggests, strcut tag is a tag added to the struct field. Using it for assistance, you can complete some additional operations, such as JSON and struct mutual conversion. If you want to change the key of the JSON string to lowercase, you can add a tag to the struct field, as follows:
type person struct{ Name string `json:"name"` Age int `json:"age"` } // The method of adding a tag to a struct field is very simple. You only need to wrap a key value pair behind the field with backquotes, such as' json:"name '. The json before the colon is a key, which can be used to obtain the corresponding name after the colon
Tip: json as a key is a convention for the json package of the Go language to parse json. It will find the corresponding value through the json key, which is used for the key value of json.
The struct tag is the key to the interaction between JSON and struct. This tag is like an alias for the struct field. How does the JSON package get this tag? This requires reflection.
type person struct{ Name string `json:"name" bson:"b_name"` Age int `json:"age" bson:"b_age"` } // Traverse the tag whose key is json in the person field for i := 0; i < pt.NumField(); i++ { sf := pt.Field(i) fmt.Println("field%s Up, json tag by %s\n", sf.Name, sf.Tag.Get("json")) } // Get the Tag on the Field, get the corresponding Field by reflection, and track it through the Field method. This method returns a StructField structure, which has a Field of Tag and holds all tags of the Field. Just call Tag Just use the get() method // Traverse the tag whose key is json and bson in the person field for i := 0; i < pt.NumField(); i++ { sf := pt.Field(i) fmt.Println("field%s Up, json tag by %s\n", sf.Name, sf.Tag.Get("json")) fmt.Println("field%s Up, bson tag by %s\n", sf.Name, sf.Tag.Get("bson")) }
The fields of the structure can have multiple tags for different scenarios, such as json conversion, bson conversion, orm parsing, etc. If there are multiple tags, use space segmentation. Different tags can be obtained by using different key s.
Convert Struct to JSON
We have understood what a struct tag is, and we are demonstrating how to use it through an example of transforming struct into json
func main(){ p := person{Name:"Running snail ", Age:18} pv := reflect.ValueOf(p) pt := reflect.TypeOf(p) // Implement struct to json by yourself jsonBuilder := string.Builder() jsonBuilder.WriteString("{") num := pt.NumField() for i :=0; i< num; i++{ jsonTag := pt.Field(i).Tag.Get("json") // Get JSON tag jsonBuilder.WriteString("\"" + jsonTag+"\"") jsonBuilder.WriteString(":") // Gets the value of the field jsonBuilder.WriteString(fmt.Sprintf("\"%v\"", pv.Field(i))) if i<num-1{ jsonBuilder.WriteString(",") } } jsonBuilder.WriteString("}") fmt.Println(jsonBuilder.String()) // Print json string } // This is a relatively simple example of converting struct to json, which can well demonstrate the use of struct.
The conversion of json string is only an application scenario of struct tag. You can use struct tag as the metadata configuration of fields in the structure and use it to do anything you want.
Law of reflection
Reflection is a method for a program to examine its own structure in computer language. It belongs to a form of meta programming. Reflection is flexible and powerful, but it is also unsafe. It can bypass many static checks of the compiler. If it is used too much, it will cause confusion. In order to help developers better understand reflection, the author of Go language summarizes the three laws of reflection. Understanding the three laws can better understand Go language reflection:
1. Any interface value interface{} Can reflect the reflected object, that is reflect.Value and reflect.Type,Through function reflect.ValueOf and reflect.TypeOf obtain 2. Reflective objects can also be restored to interface{}Variables, that is, the reversibility of the first law, pass through reflect.Value Structural Interface Method obtain 3. To modify the reflected object, the value must be settable, addressable,
Tip: any type of variable can be converted to an empty Interface interface {}, so the function reflect. In the first law Valueof and reflect The parameter of typeof is Interface {}, which means that any type of variable can be converted into a reflection object. In the second law, reflect The value returned by the Interface method of the value structure is also Interface {}, indicating that the reflection object can be restored to the corresponding type variable.
Summary:
In reflection, reflect Value corresponds to the value of the variable. If you need to perform operations related to the value of the variable, you should give priority to yo and which reflect Value, such as obtaining the value of a variable, modifying the value of a variable, etc. reflect.Type corresponds to the type of variable. If you need to perform operations related to the type of variable, you should give priority to use reflect Type, such as obtaining the fields in the structure, the method set owned by the type, etc.
Emphasis: Although reflection is powerful and can simplify programming and reduce repeated code, it will make the code more complex and chaotic. Therefore, unless it is very necessary, use them as little as possible.