当前位置:网站首页>Teach you how to use the reflect package to parse the structure of go - step 1: parameter type check

Teach you how to use the reflect package to parse the structure of go - step 1: parameter type check

2022-06-24 08:09:00 amc

Go Native encoding/json Of Unmarshal and Marshal The input parameter of the function is interface{}, And can support any struct or map type . This functional pattern , How to achieve it specifically ? This article will briefly explore the basis of this implementation pattern :reflect package .


Basic concepts

interface{}

Beginners Go, You will soon come into contact with Go A special type of :interface.Interface The meaning is : Implement the specified interface All types of functions defined in the body . for instance , We have the following interface definitions :

type Dog interface{
    Woof()
}

So as long as it is realized Woof() function ( bark ), Can be considered to be achieved Dog Type of interface . Be careful , It's all types of , Not limited to complex types or basic types . For example, we use int Redefine a type , It's OK, too :

type int FakeDog

func (d FakeDog) Woof() {
    // do hothing
}

good , Next , We will see a common way of writing :interface{},interface The word is followed by a curly bracket that contains nothing . We need to know ,Go Supports anonymous types , So this is still an interface type , This interface does not specify any functions that need to be implemented .

So from the semantic point of view, we can know , Any type conforms to the definition of this interface . On the other hand ,interface{} It can be used to express Any type . This is it. json marshaling and unmarshaling Input .

reflect

OK, Although there are interface{} Used to represent “ Any type ”, But eventually we have to parse this “ Any type ” Parameter bar ?Go Provides reflect package , Used to resolve . This is what is often mentioned in Chinese materials “ The reflex mechanism ”. Reflection can do many things , In this article, we mainly deal with the part of parsing structure .

following , We set up an experiment / Application scenarios , Step by step reflect Usage and precautions .


Experimental scenario

Various mainstream serialization / The deserialization protocol is as follows json、yaml、xml、pb There are authoritative and official libraries for everything ; But in the URL query scenario , It is not particularly perfect yet . Let's play with this scene —— URL query and struct Interturn .

First we define a function :

func Marshal(v interface{}) ([]byte, error)

Internal implementation , The logic is to parse the field information of the input parameter first , Turn into native url.Values type , Then call Encode Function to byte string output , In this way, 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 we see , The input is one interface{}, That is to say “ Any type ”. Ostensibly of any type , But in fact, not all data types support conversion , So here we need to check the input parameter type .

Here we come across the first data type we need to know :reflect.Type.reflect.Type adopt reflect.TypeOf(v) Or is it reflect.ValueOf(v).Type() get , This type contains all the data type related information 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 the demand , The input parameters we allow are structs or struct pointers . Here's what I'm using reflect.Kind type .

Kind and type What's the difference ? First we know that ,Go It's a strongly typed language ( super !), Use type newType oldType The two types defined by such a statement , Although you can use explicit type conversion , But assign directly 、 operation 、 Compare and so on , It's impossible to pass , It 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  Unable to get , The compilation information is :
// ./main.go:9:17: invalid operation: s1 == s2 (mismatched types str and string)

here , We said str and string Of type Is different . But we can say ,str and string Of kind It's the same , Why? ?Godoc Yes Kind The description of is :

  • A Kind represents the specific kind of type that a Type represents. The zero Kind is not a valid kind.

Be careful “kind of type”,kind It's right type Further classification of ,Kind Covers all Go data type , adopt Kind, We can know what the underlying type of a variable is .Kind Is an enumeration value , Here is the complete list :

  • reflect.Invaid: Indicates that it is not a legal type value
  • reflect.Bool: Boolean value , arbitrarily type xxx bool Even a further definition in series , It's all this kind. The following is similar to .
  • reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: Various signed integer types . Strictly speaking, these types of kind All different , But it 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 type
  • reflect.Float32, reflect.Float64: Floating point type
  • reflect.Complex32, reflect.Complex64: Plural type
  • reflect.Array: An array type . Note the difference from the slice
  • reflect.Chan: Go channel type
  • reflect.Func: function
  • reflect.Interface: interface type . Naturally ,interface{} Also of this type
  • reflect.Map: map type
  • reflect.Ptr: Pointer types
  • reflect.Slice: Slice type . Notice the difference from the array
  • reflect.String: string type
  • reflect.Struct: Type of structure
  • reflect.UnsafePointer: unsafe.Pointer type

It seems a little dizzy ? No problem , Let's do the simplest check here —— At this stage, we check the input parameters of the entire function , Only structs or pointer types are allowed , Nothing else is allowed .OK, Our input parameter check can be written as follows :

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 entry inspection is not finished yet . If the input parameter is a struct, So good , We can rub our hands . But if the input parameter is a pointer , Need to know , A pointer can be a pointer to any data type , So we also need to check the type of the pointer .

If the input parameter is a pointer , We can jump to reflect.Type Of Elem() function , Get it as a pointer , The type of data pointed to . Then we can check this type .

This time, , We are only allowed to point to one structure , meanwhile , The value of this structure cannot be nil. Now , The code for entering parameter validity check is quite long , Let's draw the validity check into a special function . So the above function fragment , We rewrite it like this :

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  type , That's good , Go straight back to 
		return v, nil 

	} else if k == reflect.Ptr {
		if v.IsNil() { 
			//  Pointer types , Value is empty , That's even if it's  struct  type , Can't parse 
			err = errors.New("nil pointer of a struct is not supported")
			return
		}

		//  Check whether the type pointed to by the pointer is  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 came across the second data type that we need to know :reflect.Value.reflect.Value adopt reflect.ValueOf(v) get , This type contains all the information about the target parameter , It also contains the corresponding reflect.Type. In the entry inspection stage , We only cover its three functions :

  • Type(): get reflect.Type value
  • Elem(): When the variable is of pointer type , Then the corresponding reflect.Value value
  • IsNil(): When the variable is of pointer type , You can determine whether the value is empty . In fact, you can also skip IsNil The logic goes on , So in t = t.Elem() Back , get reflect.Invalid value .

next step

This article has entered a door , I checked interface{} type . Next we need to explore reflect.Value The internal members of the structure of the format , Coming soon . Besides , The code of this article can also be found in Github Found on the , The codes in this phase correspond to Commit 915e331.

Reference material

Other articles recommend


This article adopts Creative Commons signature - Noncommercial use - Share in the same way 4.0 International licensing agreement Licensing .

The original author : amc, Welcome to reprint , But please note the source .

Original title :《 I'll teach you how to use reflect Package parsing Go The structure of the body - Step 1: Parameter type check 》

Release date :2021-06-28

Link to the original text :https://cloud.tencent.com/developer/article/1839823

原网站

版权声明
本文为[amc]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/06/20210628150052792i.html