1

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 value
  • reflect.Bool : Boolean value, any type 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 type
  • reflect.Float32 , reflect.Float64 : floating point type
  • reflect.Complex32 , reflect.Complex64 : plural types
  • reflect.Array : Array type. Note the difference with the slice
  • reflect.Chan : Go channel type
  • reflect.Func : function
  • reflect.Interface : interface type. Naturally, interface{} also belongs to this type
  • reflect.Map : map type
  • reflect.Ptr : pointer type
  • reflect.Slice : Slice type. Note the difference with the array
  • reflect.String : string type
  • reflect.Struct : structure type
  • reflect.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 the reflect.Type value
  • Elem() : When the variable is a pointer type, get the reflect.Value value corresponding to the pointer value
  • IsNil() : When the variable is a pointer type, it can be judged whether its value is empty. In fact, you can also skip IsNil and continue going down, then after t = t.Elem() , you will get the value of reflect.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

Other articles recommended


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 .


amc
924 声望223 粉丝

电子和互联网深耕多年,拥有丰富的嵌入式和服务器开发经验。现负责腾讯心悦俱乐部后台开发