Last article We have completed the analysis of the basic data types in the structure. This article is a few topics that are really troublesome and have not been dealt with in the first two articles:

  1. Anonymous member
  2. Nested structure
  3. Go slice
  4. Go array
  5. Go map

Anonymous members in the structure

Let's look back at the marshalToValues function in the previous article, which has a line " ft.Anonymous ":

func marshalToValues(in interface{}) (kv url.Values, err error) {
    // ......

    // 迭代每一个字段
    for i := 0; i < numField; i++ {
        fv := v.Field(i) // field value
        ft := t.Field(i) // field type

        if ft.Anonymous {
            // TODO: 后文再处理
            continue
        }
        
        // ......
    }

    return kv, nil
}

As mentioned earlier, this means that the current field is an anonymous field. In Go, anonymous members are often used to implement functions close to inheritance, such as:

type Dog struct{
    Name string
}

func (d *Dog) Woof() {
    // ......
}

type Husky struct{
    Dog
}

In this way, the type Husky "inherited" the Name field of Dog type Woof() function.

But it should be noted that in Go, this is not true inheritance. When we analyze Husky through reflect, we will find that it contains a Dog , and this structure will enter the branch if ft.Anonymous {}

The second point to note is that in Go, not only struct can be used as an anonymous member, in fact, any type can be anonymous. Therefore, this situation needs to be distinguished in the code.

OK, after knowing the above points of attention, we can deal with the situation of anonymous structures. If the main purpose of an anonymous structure is for the effect of inheritance, then our attitude towards the members of the anonymous structure is the same as the attitude towards the ordinary members of the structure itself. marshalToValues we have implemented, and extract the iterative logic separately to facilitate recursion-pay attention to the first conditional judgment code block of the readFieldToKV

func marshalToValues(in interface{}) (kv url.Values, err error) {
    // ......

    // 迭代每一个字段
    for i := 0; i < numField; i++ {
        fv := v.Field(i) // field value
        ft := t.Field(i) // field type

        readFieldToKV(&fv, &ft, kv) // 主要逻辑抽出到函数中进行处理
    }

    return kv, nil
}

func readFieldToKV(fv *reflect.Value, ft *reflect.StructField, kv url.Values) {
    if ft.Anonymous {
        numField := fv.NumField()
        for i := 0; i < numField; i++ {
            ffv := fv.Field(i)
            fft := ft.Type.Field(i)

            readFieldToKV(&ffv, &fft, kv)
        }
        return
    }
    if !fv.CanInterface() {
        return
    }
    
    // ...... 原来的 for 循环中的主逻辑
}

Slices and arrays in structures

In the previous section, we marshalToValues the logic of readFieldToKV function 06145aaa3e26e3. This function first judges if ft.Anonymous , that is, whether it is anonymous; then judges if !fv.CanInterface() , that is, whether it can be exported.

Going further down, we deal with every member of the structure. In the previous article, we have dealt with all the simple data types, but there are still many variable types that carry valid data that we have not dealt with yet. In this section, let's take a look at how to do slices and arrays.

First of all, in this article, we stipulate that for arrays, only arrays whose members are basic types (bool, numbers, strings, Boolean values) are supported, and the so-called "arbitrary types" (that is, interface{} ) and structures ( struct ) are not supported. Array.

The reason is that we are prepared to use the dot we delimiter to distinguish arrays within the array, i.e., using such msg.data represented msg structural body data members. The URL query uses the same key to appear multiple times to realize the array type. If msg.data appears repeatedly, should we interpret it as msg[n].data or msg.data[n] ?

In order to implement this piece of code, we modify the previous readFieldToKV to:

func readFieldToKV(fv *reflect.Value, ft *reflect.StructField, kv url.Values) {
    if ft.Anonymous {
        numField := fv.NumField()
        for i := 0; i < numField; i++ {
            ffv := fv.Field(i)
            fft := ft.Type.Field(i)

            readFieldToKV(&ffv, &fft, kv)
        }
        return
    }
    if !fv.CanInterface() {
        return
    }

    tg := readTag(ft, "url")
    if tg.Name() == "-" {
        return
    }

    // 将写 KV 的功能独立成一个函数
    readFieldValToKV(fv, tg, kv)
}

Then we look at the readFieldValToKV called in this function. This function is about 50 lines. Let's look at it in several parts:

func readFieldValToKV(v *reflect.Value, tg tags, kv url.Values) {
    key := tg.Name()
    val := ""
    var vals []string
    omitempty := tg.Has("omitempty")
    isSliceOrArray := false

    switch v.Type().Kind() {
    // ......
    // 代码块 1
    // ......

    // ......
    // 代码块 2
    // ......
    }

    // 数组使用 Add 函数
    if isSliceOrArray {
        for _, v := range vals {
            kv.Add(key, v)
        }
        return
    }

    if val == "" && omitempty {
        return
    }
    kv.Set(key, val)
}

The code block 1 content is to convert the basic type of data to val string type variable value. There is nothing to say, the first two articles have already explained

The code block 2 is the analysis of slices and arrays, and the content is as follows:

    case reflect.Slice, reflect.Array:
        isSliceOrArray = true
        elemTy := v.Type().Elem()
        switch elemTy.Kind() {
        default:
            // 什么也不做,omitempty 对数组而言没有意义
        case reflect.String:
            vals = readStringArray(v)
        case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
            vals = readIntArray(v)
        case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
            vals = readUintArray(v)
        case reflect.Bool:
            vals = readBoolArray(v)
        case reflect.Float64, reflect.Float32:
            vals = readFloatArray(v)
        }

We take readStringArray as an example:

func readStringArray(v *reflect.Value) (vals []string) {
    count := v.Len()                    // Len() 函数

    for i := 0; i < count; i++ {
        child := v.Index(i)                // Index() 函数
        s := child.String()
        vals = append(vals, s)
    }

    return
}

It's clear at a glance. Two functions reflect.Value are involved here:

  • Len() : For slices, arrays, and even maps, this function returns the number of its members
  • Index(int) : For slices and arrays, this function returns the position of its members

The subsequent operation is the same as the standard numeric field, read the value in reflect.Value

The code so far corresponds to the 40d0693 version on Github. Readers can check diff understand what code changes we have made to support anonymous members and slice/number types compared to the previous article.

Structure in structure

I have mentioned it briefly in the previous article: we intend to use a similar dot operator mode to deal with non-anonymous, exportable structures in structures. For JSON, this is equivalent to "objects in objects".

From a technical point of view, the required knowledge is actually already there. In this section, in order to support the function of the structure in the structure, we need to make further adjustments to the source file. The main functions are as follows: The following:

  • Add prefix parameters to related functions and support recursive calls to achieve multiple levels of nesting
  • The common modes of structure in structure, including structure and structure pointer, need to be dealt with separately

The code version that added the struct in struct function is closely following the previous version 070cb3b , readers can check the diff difference, you can see that my changes are actually not many, basically corresponding to the above two, short Support for struct in struct has been realized in just ten lines.

Go map

This is the last of the complex data types. Here we explain how to determine whether the object is a map reflect.Value , and how to get the key and value values reflect.Value

First of all, we sort out, if we encounter the map type, our judgment logic:

  1. First determine the key type of the map, we only support the map whose key is string
  2. Then determine the value type of the map:

    • Needless to say if it is a basic data type, support-such as map[string]string , map[string]int the like
    • If it is struct, it is also supported, just treat it as struct in struct
    • If it is a slice or an array, it is also processed according to the processing mode in the second subsection of this article
    • If it is of interface{} , then you need to determine whether the type of each value is supported one by one

OK, here we first introduce reflect.Value needs to use when processing map. In able to determine the current reflect.Value in kind equal reflect.Map under the premise:

  • Determine whether the key is of type String: if v.Type().Key().Kind() != reflect.String {return} , which is reflect.Type of Key() function, you can get key type.
  • To get the type of value, use: v.Type().Elem() to return a new reflect.Type , which represents the value type of the map.
  • Get all the key values in the map, use: v.MapKeys() , return a type []reflect.Value
  • Obtain the value in the map according to the key: v.MapIndex(k) , enter the parameter

In addition, if you want to iterate the kv in the map, you can also use the MapRange function, readers can 16145aaa3e2be5

There is not much code to add, just add a "code block 3" after the "code block 2" readFieldValToKV

    case reflect.Map:
        if v.Type().Key().Kind() != reflect.String {
            return // 不支持,直接跳过
        }

        keys := v.MapKeys()
        for _, k := range keys {
            subV := v.MapIndex(k)
            if subV.Kind() == reflect.Interface {
                subV = reflect.ValueOf(subV.Interface())
            }
            readFieldValToKV(&subV, tags{k.String()}, kv, key)
        }
        return

Why add a if subV.Kind() == reflect.Interface ? It is mainly for map[string]interface{} , because the value type of this map is reflect.Interface . If you want to get the value of the underlying data type, you need to add another sentence subV = reflect.ValueOf(subV.Interface()) , so that reflect.Value will only be Kind Is its true type.

end

The code so far corresponds to the a18ab4a version. At this point, analyzing the contents of the structure by reflect is the end of the explanation.

We only talked about the content of marshal. As for the process of unmarshal, the analytical parameter types and structures are similar. The only difference is how to assign values interface{} The author strives to write about this related content in the next article.

Related articles recommended


This article uses the Creative Commons Attribution-Non-Commercial Use-Same Method Sharing 4.0 International License Agreement for licensing.

This article was first published in cloud + community , which is also the blog of himself

Original author: amc , welcome to reprint, but please indicate the source.

Original title: "Teach you how to use reflect package to parse Go structure-Step 3: Complex type checking"

Release Date: 2021-09-18

Original link: https://segmentfault.com/a/1190000040707732



amc
924 声望223 粉丝

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