Reflection is an important aspect of all object-oriented languages, and it provides flexible manipulation possibilities for developers. Reflection can be used to obtain information about different objects/structures, develop different strategies, and achieve complex operations.
The reflection operation thinking of GO is quite different from that of Java, and I have encountered some difficulties in learning it, so I will record them to deepen my impression.
Task: Read Excel files and generate corresponding objects according to the incoming structure.
Data preparation
What I prepared was the student information sent by the previous school (please don’t worry about why I use this data, I found this casually).
From the fields in the Excel table, we can create a Student structure that contains the corresponding fields:
// Student structure
type Student struct {
Sno int / / student id
Sname string / / name
Sclass string / / class
Sbank string / / card number
}
Copy the code
Get structure information
The operation for the entry function looks like this:
func main(a) {
var path string = "D:/GoCode/go-datafilter/reources/xxx.xlsx"
dataType := util.ReadDataFromExcel(path, &Student{})
for _, data := range dataType.Data {
fmt.Println(data)
}
}
Copy the code
It’s as simple as passing two arguments to the ReadDataFromExcel method:
filePath
: Path to the Excel file to readtargetStruct
: structure that requires reflection
In the ReadDataFromExcel method, we need to do three steps:
- Get structure information
- Obtain Excel information
- Create objects through reflection
First post the complete code to get structure information, and then analyze it:
// Field type slice
var fieldType = make([]string.0)
// Field name slice
var fieldName = make([]string.0)
// Read the name and type of the structure field
func getFieldNameAndType(typeof reflect.Type) reflect.Type {
if typeof.Kind() == reflect.Ptr {
typeof = typeof.Elem()
}
iftypeof.Kind() ! = reflect.Struct {panic("bab type")
}
fieldsNum := typeof.NumField()
for i := 0; i < fieldsNum; i++ {
fieldType = append(fieldType, typeof.Field(i).Type.Name())
fieldName = append(fieldName, typeof.Field(i).Name)
}
datatype.FieldName = fieldName
datatype.FieldType = fieldType
return typeof
}
Copy the code
In the Go language, it provides a reflect package to assist us in reflection operations, which encapsulates several methods.
There are two methods to classify reflected objects in Go, one is based on Type Type and the other is based on Type Kind. Kind is a broader concept than Type. Type refers to the native data types of the system, such as int, String, bool, etc., as well as the types defined by the Type keyword, and Kind is the Type to which the Type belongs. As shown in the following example:
func main(a) { student := Student{} typeOf := reflect.TypeOf(student) fmt.Println(typeOf.Kind()) // struct: it is a struct type // Name(): Returns a string representing the type Name fmt.Println(typeOf.Name()) // Student: This is a Student struct type } Copy the code
For reference types such as pointer types map, slice, and chan, there are distinct classes in the Kind definition. For pointer types, Name returns the built-in primitive type and the type defined in the package. For undefined types, it returns null. Kind has a built-in Ptr for representation. That’s why we see a judgment like this in most code:
if typeof.Kind() == reflect.Ptr { typeof = typeof.Elem() } iftypeof.Kind() ! = reflect.Struct {panic("bad type")}Copy the code
Elem() returns the element type to which the reflection pointer points, equivalent to an * operation on a pointer type variable.
After checking that typeOf is available, we can use the reflect.type method to get the information we need. There are four main methods used in the above code:
NumField()
: Gets the number of fieldsField(index int)
: Obtains the corresponding field type through the indexName()
: Gets the name of the specified fieldType()
: Gets the category of the specified field
There are three ways to get a field:
-
Field(intdex int)/FieldByIndex(indexs int[])
For FieldByIndex methods, it is used for nested fetching, such that FieldByIndex(1, 2) is equivalent to Field(1).field (2).
-
Get by fieldName: FieldByName(fieldName string)
-
FieldByNameFunc(match func(string) bool)
Create objects through reflection
Now that we have the structure information, it’s time for a happy new object. The overall code looks like this:
func ReadDataFromExcel(path string, mytype interface{}) []interface{} {
/ /...
for _, sheet := range file.Sheets{
for _, row := range sheet.Rows { // Read each line
if idx == 0 { // Do not read the first row of the Excel table
idx++
continue
}
newStruct := reflect.New(typeof)
for idx, cell := range row.Cells { // Each cell reads
text := strings.TrimSpace(cell.String())
newObject(text, newStruct, idx, idx)
}
data = append(data, newStruct)
}
}
/ /...
}
// Assign a value to the reflection object
func newObject(value string, newStruct reflect.Value, nameIdx, typeIdx int) {
switch fieldType[typeIdx] {
case "int"."int64"."int32":
num, err := strconv.Atoi(value)
iferr ! =nil {
fmt.Println(err)
}
newStruct.Elem().FieldByName(fieldName[nameIdx]).SetInt(reflect.ValueOf(num).Int())
case "string":
newStruct.Elem().FieldByName(fieldName[nameIdx]).SetString(reflect.ValueOf(value).String())
case "bool":
newStruct.Elem().FieldByName(fieldName[nameIdx]).SetBool(reflect.ValueOf(value).Bool())
case "float32"."float64":
newStruct.Elem().FieldByName(fieldName[nameIdx]).SetFloat(reflect.ValueOf(value).Float())
default:
newStruct.Elem().FieldByName(fieldName[nameIdx]).SetString(reflect.ValueOf(value).String())
}
}
Copy the code
First of all, we like the use of a Class in Java. GetDeclaredConstructor (). The newInstance () to create an object to create the same structure: newStruct: = reflect. The New typeof ().
Each row corresponds to a structure, and the value of each cell in each row corresponds to the value of the corresponding field in the structure.
So in the newObjec method, you first determine what type the value of the corresponding cell is and then convert the type. Because Go is a strongly typed language, the conversion steps are cumbersome, but it looks neat.
We can also see that there is no need to create a separate [field > type] mapping of the structure, or a slice. Just use the Field method mentioned above. That’s true. I’m doing this for other purposes, but it’s not for this article.
The value.eelem () method is similar to the type.eelem () method in that it retrieves the object to which the interface contains a Value or pointer.
For setting the value of the reflected object, we can set it by SetXXX(). The mode I use here is almost SetXXX(reflect.valueof (value).xxx ()).
TypeOf is the type that gets the Value of the input parameter, and Value is the Value that gets the data in the input parameter.
When reflect.valueof (value) is executed, it will get a variable of type relect.Value. The real content of the Interface variable can be obtained through its own Interface() method, and then converted to the original real type by type judgment. Such as:
func main(a) { student := Student{ 1."zs"."110"."001", } typeOf := reflect.ValueOf(student) fmt.Println(typeOf.Interface().(Student)) {1 zs 110 001} } Copy the code
After the above program is run, the result is shown as follows: