introduction
Go's native encoding/json
, Unmarshal
and Marshal
functions have interface{}
, and can support any struct or map type. How is this functional mode implemented? reflect
explore the basis of this realization mode: 060e91b530c691 package.
basic concepts
interface{}
interface
Go, you will soon come into contact with a special type of Go: 060e91b530c6d4. The meaning of Interface is to implement all types of functions defined in the specified interface body. For example, we have the following interface definition:
type Dog interface{
Woof()
}
So as long as the Woof()
function (barking call) is realized, it can be considered as a type Dog
Note that it is all types, not limited to complex types or basic types. For example, if we use int
to redefine a type, it is also possible:
type int FakeDog
func (d FakeDog) Woof() {
// do hothing
}
Ok, next, we will see a common writing: interface{}
, the word interface is followed by a curly brace that does not contain any content. We need to know that Go supports anonymous types, so this is still an interface type, but this interface does not specify any functions that need to be implemented.
So semantically, we can know that any type conforms to the definition of this interface. Conversely, interface{}
can be used to represent any type . This is the entry of json marshaling and unmarshaling.
reflect
OK, although interface{}
used to represent "arbitrary type", we finally have to parse this "arbitrary type" parameter, right? Go provides the reflect
package for parsing. This is the "reflection mechanism" often mentioned in Chinese materials. Reflection can do many things. In this article, we mainly deal with the part of analyzing the structure.
Below, we set up an experiment/application scenario to introduce the usage and precautions of reflect step by step.
Experimental scene
Various mainstream serialization/deserialization protocols such as json, yaml, xml, pb, etc. have authoritative and official libraries; however, in the URL query scenario, they are relatively incomplete. Let's play with this scene-URL query and struct are converted to each other.
First we define a function:
func Marshal(v interface{}) ([]byte, error)
In internal implementation, the logic is to first parse the url.Values
parameter field information, convert it to the native 060e91b530c7e2 type, and then call the Encode
function to convert it to a byte string output, so that we don’t have to worry about the escape of special characters.
func Marshal(v interface{}) ([]byte, error) {
kv, err := marshalToValues(v)
if err != nil {
return nil, err
}
s := kv.Encode()
return []byte(s), nil
}
func marshalToValues(in interface{}) (kv url.Values, err error) {
// ......
}
Input parameter type check-reflect.Type
First of all, we see that the interface{}
parameter is a 060e91b530c81f, which is "any type". On the surface it is an arbitrary type, but in fact not all data types support conversion, so here we need to check the input parameter type.
Here we encounter the first data type that needs to be recognized: reflect.Type
. reflect.Type
obtained through reflect.TypeOf(v)
reflect.ValueOf(v).Type()
. This type contains all the information related to the data type of the input parameter:
func marshalToValues(in interface{}) (kv url.Values, err error) {
if in == nil {
return nil, errors.New("no data provided")
}
v := reflect.ValueOf(in)
t := v.Type()
// ......
}
According to requirements, the input parameters we allow are structures or structure pointers. reflect.Kind
type is used here.
What is the difference between Kind and type? First of all, we know that Go is a strongly typed language (super!). type newType oldType
can be explicitly type converted, but when assignments, operations, comparisons, etc. are directly performed, they are Unable to pass, may even cause panic:
package main
import "fmt"
func main() {
type str string
s1 := str("I am a str")
s2 := "I am a string"
fmt.Println(s1 == s2)
}
// go run 无法通过,编译信息为:
// ./main.go:9:17: invalid operation: s1 == s2 (mismatched types str and string)
Here, we say that str
and string
of type
are different. But we can say, str
and string
of kind
are the same, Why? Godoc's description of Kind is:
- A Kind represents the specific kind of type that a Type represents. The zero Kind is not a valid kind.
Pay attention to "kind of type". Kind is a further classification of type. Kind covers all Go data types. Through Kind, we can know what the underlying type of a variable is. Kind is an enumerated value, the following is the complete list:
reflect.Invaid
: indicates that it is not a legal type valuereflect.Bool
: Boolean value, anytype xxx bool
or even further concatenation is this kind. The following is similar.reflect.Int
,reflect.Int64
,reflect.Int32
,reflect.Int16
,reflect.Int8
: various signed integer types. Strictly speaking, these types of kind are different, but they can often be handled together. The reason will be mentioned later.reflect.Uint
,reflect.Uint64
,reflect.Uint32
,reflect.Uint16
,reflect.Uint8
: Various unsigned integer types.reflect.Uintptr
:uintptr
typereflect.Float32
,reflect.Float64
: floating point typereflect.Complex32
,reflect.Complex64
: plural typesreflect.Array
: Array type. Note the difference with the slicereflect.Chan
: Go channel typereflect.Func
: functionreflect.Interface
:interface
type. Naturally,interface{}
also belongs to this typereflect.Map
:map
typereflect.Ptr
: pointer typereflect.Slice
: Slice type. Note the difference with the arrayreflect.String
:string
typereflect.Struct
: structure typereflect.UnsafePointer
:unsafe.Pointer
type
Looks a bit dazzling? It doesn't matter, let's do the simplest check first-at this stage, we check the input parameters of the entire function. Only structures or pointer types are allowed, and nothing else is allowed. OK, our input parameter check can be written like this:
func marshalToValues(in interface{}) (kv url.Values, err error) {
// ......
v := reflect.ValueOf(in)
t := v.Type()
if k := t.Kind(); k == reflect.Struct || k == reflect.Ptr {
// OK
} else {
return nil, fmt.Errorf("invalid type of input: %v", t)
}
// ......
}
The participation check is not over yet. If the entry is a struct, then it is very good, we can gear up. But if the input parameter is a pointer, you must know that the pointer may be a pointer of any data type, so we also need to check the type of the pointer.
reflect.Type
parameter is a pointer, we can jump to the Elem()
obtain the data type pointed to by it as a pointer. Then we can check this type again.
This time, we are only allowed to point to one structure, and at the same time, the value of this structure cannot be nil. As a result, the code for the legality check of the input parameters is quite long. Let's extract the legality check into a special function. Therefore, the above function fragment, we rewrite it as follows:
func marshalToValues(in interface{}) (kv url.Values, err error) {
v, err := validateMarshalParam(in)
if err != nil {
return nil, err
}
// ......
}
func validateMarshalParam(in interface{}) (v reflect.Value, err error) {
if in == nil {
err = errors.New("no data provided")
return
}
v = reflect.ValueOf(in)
t := v.Type()
if k := t.Kind(); k == reflect.Struct {
// struct 类型,那敢情好,直接返回
return v, nil
} else if k == reflect.Ptr {
if v.IsNil() {
// 指针类型,值为空,那就算是 struct 类型,也无法解析
err = errors.New("nil pointer of a struct is not supported")
return
}
// 检查指针指向的类型是不是 struct
t = t.Elem()
if t.Kind() != reflect.Struct {
err = fmt.Errorf("invalid type of input: %v", t)
return
}
return v.Elem(), nil
}
err = fmt.Errorf("invalid type of input: %v", t)
return
}
Input parameter value iteration-reflect.Value
From the previous function, we encountered the second data type that needs to be recognized: reflect.Value
. reflect.Value
obtained through reflect.ValueOf(v)
, this type contains all the information of the target parameter, which also contains the reflect.Type
corresponding to this variable. In the entry check stage, we only involved its three functions:
Type()
: Obtain thereflect.Type
valueElem()
: When the variable is a pointer type, get thereflect.Value
value corresponding to the pointer valueIsNil()
: When the variable is a pointer type, it can be judged whether its value is empty. In fact, you can also skipIsNil
and continue going down, then aftert = t.Elem()
, you will get the value ofreflect.Invalid
Next step
This article has entered a door, check the interface{}
parameters of type 060e91b530cf47. Next, we need to explore the internal members of the structure in reflect.Value
In addition, the code in this article can also be found on Github . The code at this stage corresponds to Commit 915e331 .
Reference
- Checking reflect.Kind on interface{} return invalid result
- Three laws and best practices of Go reflection-3.
Other articles recommended
- Go Language Design and Implementation-4.3 Reflection
- still using
map[string]interface{}
process JSON? Tell you a more efficient method-jsonvalue - What's wrong with the native json package of Go language? How to better handle JSON data?
- you to use reflect package to parse Go structure-Step 2: Structure member traversal
This article uses Creative Commons Attribution-Non-commercial Use-Same Method Sharing 4.0 International License Agreement for licensing.
Original author: amc , the original text was published in cloud + community , which is also the blog himself Welcome to reprint, but please indicate the source.
Original title: "Teach you to use reflect package to parse Go structure-Step 1: Parameter type check"
Release Date: 2021-06-28
Original link: https://cloud.tencent.com/developer/article/1839823 .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。