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:
- Anonymous member
- Nested structure
- Go slice
- Go array
- 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 membersIndex(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:
- First determine the key type of the map, we only support the map whose key is string
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
- Needless to say if it is a basic data type, support-such as
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 isreflect.Type
ofKey()
function, you can get key type. - To get the type of value, use:
v.Type().Elem()
to return a newreflect.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
- GO programming mode: slice, interface, time and performance
- still using map "string" interface{} to 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?
- teach you how to use reflect package to parse Go structure-Step 1: Parameter type check
- teach you how to use reflect package to parse Go structure-Step 2: Structure member traversal
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。