The introduction

Go’s native encoding/json Unmarshal and Marshal functions take interface{} arguments and can support arbitrary struct or map types. How does this function pattern work? This article briefly explores the basis of this implementation pattern: the Reflect package.

The basic concept

interface{}

As a beginner of Go, you will soon be exposed to a special type of Go: Interface. Implement all types of functions defined in the body of an Interface. For example, we have the following interface definition:

type Dog interface{
    Woof()
}

So anyone who implements the Woof() function (Woof Woof) can be considered to implement the type of the Dog interface. Note that all types, not just complex types or primitive types. For example, if we redefine a type with int, it is also possible:

type int FakeDog

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

OK, now, we’ll see another common notation: interface{}, with the word interface followed by a curly brace that contains nothing. It should be noted that Go supports anonymous types, so this is still an interface type, except that the interface does not specify any functions that need to be implemented.

Semantically, then, we can see that any type conforms to the definition of this interface. On the other hand, interface{} can be used to represent any type. This is the input parameter for JSON marshaling and unmarshaling.

reflect

OK, so we have interface{} for “any type”, but we have to resolve this “any type” parameter eventually, right? Go provides the Reflect package for parsing. This is often referred to in Chinese sources as the “reflex mechanism”. Reflection can do a lot of things, and in this article we’ll focus on parsing structures.

Below, we’ll set up an experiment/application scenario to introduce the use and considerations of Reflect step by step.

The experimental scene

Various major serialization/deserialization protocols, such as JSON, YAML, XML, PB, etc., have authoritative and official libraries; However, in the case of URL Query, it is relatively incomplete. Let’s play around with a scenario where URL Query and struct interchange.

First we define a function:

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

Internally, the logic is to parse the input field information, convert it to the native URL.VALUES type, and then call the Encode function to convert it to byte string output, so we don’t have to worry about escaping 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) { // ...... }

Enter the input Type check — reflect.type

First we see that the input parameter is an interface{}, which is “any type”. Appearance is arbitrary, but in fact not all data types support conversion, so here we need to check the input type.

Here we come to our first data Type to be aware of: reflect.type. Reflect.type is obtained by either reflect.typeof (v) or reflect.valueof (v).type (). This Type contains all the data Type related information for the input:

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()

    // ......
}

As required, the input parameters we allow are structures or Pointers to structures. The shame.Kind type is used here.

What’s the difference between Kind and type? First we know that Go is a strongly typed language (super!). Type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType = type newType oldType

package main import "fmt" func main() { type str string s1 := str("I am a str") s2 := "I am a string" fmt.Println(s1 == // main. Go :9:17: invalid operation: s1 == s2 (mismatched types STR and string)

Here, we say that STR and string have different types. But we can say that STR and string are the same kind. 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.

Note that “kind of type” is a further classification of type. Kind covers all of the GO data types. By kind, we know what the underlying type of a variable is. Kind is an enumerated value, and here is the complete list:

  • reflect.Invaid: indicates that it is not a valid type value
  • reflect.Bool: Boolean value, arbitrarytype xxx boolAnd even further down the line, the definition is this kind. The following is similar.
  • reflect.Int.reflect.Int64.reflect.Int32.reflect.Int16.reflect.Int8: Various signed integer types. Strictly speaking these kinds are different, but they can often be treated together. The reasons will be mentioned later.
  • reflect.Uint.reflect.Uint64.reflect.Uint32.reflect.Uint16.reflect.Uint8: Various unsigned integer types.
  • reflect.Uintptr: uintptrtype
  • reflect.Float32.reflect.Float64: Floating-point type
  • reflect.Complex32.reflect.Complex64: Plural type
  • reflect.Array: Array type. Notice the difference with slicing
  • reflect.Chan: Go Channel type
  • reflect.Func: function
  • reflect.Interface: interfaceType. Naturally,interface{}It also falls into this category
  • reflect.Map: maptype
  • reflect.Ptr: Pointer type
  • reflect.Slice: Slice type. Notice the difference with arrays
  • reflect.String: stringtype
  • reflect.Struct: Structure type
  • reflect.UnsafePointer: unsafe.Pointertype

Seems a little giddy? That’s OK, let’s do the simplest of checks first — at this stage we’re checking the entire function for arguments, only allowing structures or pointer types, and nothing else. OK, our entry 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)
    }

    // ......
}

This is not the end of the input check. If the entry parameter is a struct, then fine, we can rub our hands in. But if the input argument is a pointer, remember, a pointer could be a pointer to any data type, so we also need to check the type of the pointer.

If the input argument is a pointer, we can skip the reflect.type Elem() function to get it as a pointer to the data Type to which it points. Then we can check the type again.

This time, we’re only allowed to refer to one structure, and the value of that structure can’t be nil. As a result, the validity check code is quite long, let’s split the validity check into a special function. So the above function fragment is rewritten 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(); Void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void void So that's a struct type, Err = errors.New("nil pointer of a struct is not supported") return} struct t = t.lem () 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 come across a second data type that we need to be aware of: reflect.value. Reflect.value is obtained by reflect.valueOf (v). This Type contains all the information of the target parameter, including the reflect.type corresponding to this variable. In the input checking phase, we covered only three of its functions:

  • Type():reflect.Type
  • Elem(): When a variable is of pointer type, the value corresponding to its pointer value is obtainedreflect.Value
  • IsNil(): When a variable is of pointer type, it can be determined whether its value is null. You can actually skip itIsNilThe logic continues to go down, then int = t.Elem()In the back, you’ll get itreflect.InvalidValue.

The next step

This article inserts a gate to check for interface{} type input parameters. Next we need to explore the inner members of the structure in reflect.Value format, so stay tuned. The code for this article is also available on GitHub. The code for this phase corresponds to Commit 915E331.

The resources

  • Checking reflect.Kind on interface{} return invalid result
  • 3. Reflection.Value Data Structures

Other Articles Recommended

  • Go Language Design and Implementation – 4.3 Reflection
  • Still usemap[string]interface{}Deal with JSON? Here’s a more efficient way to do it — JSONValue
  • What’s wrong with the Go native JSON package? How do you better handle JSON data?
  • Learn how to use the Reflect package to explain the structure of Go – Step 2: Walk through the structure members

This article is licensed under a Creative Commons Attribution – Non-Commercial – Share Like 4.0 international license.

Author: AMC, originally published in Cloud + community, is also my blog. Welcome to reprint, but please indicate the source.

Step 1: Analyze Go’s structure with the Reflect package Step 1: Check the type of parameters

Post Date: 2021-06-28

The original link: https://cloud.tencent.com/developer/article/1839823.