当前位置:网站首页>Teach you how to use the reflect package to parse the structure of go - step 2: structure member traversal

Teach you how to use the reflect package to parse the structure of go - step 2: structure member traversal

2022-06-24 07:42:00 amc

Last article We learned how to use reflect Check the type of a parameter . This article , We get a structure type , Then we need to explore the internal structure of the structure and its corresponding values .


Structure member iteration

Last article , our marshal The function currently looks like this :

func marshalToValues(in interface{}) (kv url.Values, err error) {
	v, err := validateMarshalParam(in)
	if err != nil {
		return nil, err
	}

	// ......
}

Come here , We got one struct Of reflect.Value Variable . Next , Let's add a few more lines of code , It's like this :

func marshalToValues(in interface{}) (kv url.Values, err error) {
	v, err := validateMarshalParam(in)
	if err != nil {
		return nil, err
	}

	t := v.Type()
	numField := t.NumField()

	kv = url.Values{}

	//  Iterate over each field 
	for i := 0; i < numField; i++ {
		fv := v.Field(i) // field value
		ft := t.Field(i) // field type

		// ......
	}

	return kv, nil
}

Variable t It's a reflect.Type, Indicates the type of the current variable , Its function NumField(), about struct Type variable , Indicates the number of all member fields under the variable .

Member resolution process

Iterate over each field in the structure , Then see fv := v.Field(i) and ft := t.Field(i). among fv A variable is reflect.Value type , After the last article , The reader is already familiar with . But variables tv It is reflect.StructField type , This is a new type . It represents the attributes of the field type in the structure .

For a structure member , In addition to the field body type , We will also check its other properties , It needs to use fv and ft Several parameters of variables , As shown below :

Anonymous members

Go In the structure of , Support anonymous members . Handling of anonymous members , There are several points to consider . Let's skip , It will be specifically explained later , So the code is as follows :

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

	//  Iterate over each field 
	for i := 0; i < numField; i++ {
		fv := v.Field(i) // field value
		ft := t.Field(i) // field type

		if ft.Anonymous { //  Whether anonymous members 
			// TODO:  We will deal with it later 
			continue
		}

		// ......
	}

	return kv, nil
}

Members cannot be exported

Go In the structure of , share ( Exportable ) Members start with capital letters , And private ( Cannot export ) Members start with lowercase letters . according to Go The Convention of , It's going on marshal / unmarshal In operation , Private members are not handled , So these members , We should filter out non - processing .

But there is one exception : Anonymous members themselves may not be exportable , This needs to be handled differently . So we put the processing logic of anonymous members in front . So the code here is rewritten as follows :

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

	//  Iterate over each field 
	for i := 0; i < numField; i++ {
		fv := v.Field(i) // field value
		ft := t.Field(i) // field type

		if ft.Anonymous { //  Whether anonymous members 
			// TODO:  We will deal with it later 
			continue
		}
		if !fv.CanInterface() { //  Can I export , Use  fv  Variable  CanInterface  Function to judge 
			continue
		}

		// ......
	}

	return kv, nil
}

Go tag analysis

We know , stay Go Many of the marshal / unmarshal Function , For structure variables and byte streams key Mapping of values , Through the label in the structure , That is to say tag To achieve . For example, the following definition :

type Pet struct {
	OwnerID string `url:"owner_id,omitempty" json:"ownerID"`
	Name    string `url:",omitempty"`
	Sex     int
}

Through this tag, In the byte stream ownerID and OwnerID Variables are associated . hinder omitempty As tag Additional instructions for , Indicates when the field value is equal to the null value , The value of this field is not encoded .

as for Name Field , Because it is not clearly specified tag, Then the default setting is key Map to the same... As the variable name Name.

therefore , Since we write it ourselves marshal / unmarshal function , Obviously, it should also follow such a reference pattern . Let's write a short piece of code to parse this field tag Information , Participation is *reflect.StructField type , Implement the following functions :

  • If specified tag Configuration is not empty , There are two cases : - Everything was good. There was content before , So the data before the comma is key name - There is nothing before the comma , In this case, the name of the field is used as tag
  • If specified tag Configuration does not exist , The name of the field is used as tag
  • Support to obtain other parameters
type tags []string

func readTag(ft *reflect.StructField, tag string) tags {
	tg := ft.Tag.Get(tag)

	//  If  tag  Configuration is not empty , Then return to 
	if tg != "" {
		res := strings.Split(tg, ",")
		if res[0] != "" {
			return res
		}
		return append(tags{ft.Name}, res[1:]...)
	}
	//  If  tag  Configuration is empty , Then return the field name 
	return tags{ft.Name}
}

// Name  At present  tag  The first field defined , This field must be a name 
func (tg tags) Name() string {
	return tg[0]
}

// Has  Judge the present  tag  Whether some additional parameter values are configured , such as  omitempty
func (tg tags) Has(opt string) bool {
	for i := 1; i < len(tg); i++ {
		t := tg[i]
		if t == opt {
			return true
		}
	}
	return false
}

Configuration above , It covers the new Pet Several of the types tag situation .

here , We just need to add another filter branch to continue down . This filter branch is : When tag The configuration value is equal to - when , according to Go The agreement of , This means that the change field is ignored :

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

	//  Iterate over each field 
	for i := 0; i < numField; i++ {
		fv := v.Field(i) // field value
		ft := t.Field(i) // field type

		if ft.Anonymous { //  Whether anonymous members 
			// TODO:  We will deal with it later 
			continue
		}
		if !fv.CanInterface() { //  Can I export , Use  fv  Variable  CanInterface  Function to judge 
			continue
		}

		tg := readTag(&ft, "url")
		if tg.Name() == "-" { // -  Indicates that the current field is ignored 
			continue
		}

		// ......
	}

	return kv, nil
}

Structure member value reading

After the previous filtration , We are here , You can already get everything you need to deal with 、 Legal structure field information , The next step is to get the value of each structure member .

This step we use fv Variable , This is a reflect.Value type . For different data types , Different methods of taking values .

Here, please review reflect.Kind type , At this stage , For the time being, let's deal with the following data types :

  • character string
  • integer
  • floating-point
  • Boolean type

Other types are more complex , We will explain further later .

It's no use saying more , This short piece of code is not long , As shown below :

func readFieldVal(v *reflect.Value, tag tags) (s string, ok bool) {
	switch v.Type().Kind() {
	default:
		return "", false //  Unsupported variable type , Go straight back to 

	case reflect.String:
		return v.String(), true

	case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
		return strconv.FormatInt(v.Int(), 10), true

	case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
		return strconv.FormatUint(v.Uint(), 10), true

	case reflect.Bool:
		return fmt.Sprintf("%v", v.Bool()), true

	case reflect.Float64, reflect.Float32:
		return strconv.FormatFloat(v.Float(), 'f', -1, 64), true
	}
}

The code shows various types of value taking functions :

type

Value function

remarks

character string

v.String()

Unsigned integer

v.Uint()

No matter how wide the seat is , Unified access uint64 type

signed int

v.Int()

No matter how wide the seat is , Unified access int64 type

reflect.Bool

v.Bool()

Floating point numbers

v.Float()

Unified access float64 type

therefore , Soon! , The body of our iterated function , It's done. :

func marshalToValues(in interface{}) (kv url.Values, err error) {
	v, err := validateMarshalParam(in)
	if err != nil {
		return nil, err
	}

	t := v.Type()
	numField := t.NumField() //  The number of all fields under the structure 

	kv = url.Values{}

	//  Iterate over each field 
	for i := 0; i < numField; i++ {
		fv := v.Field(i) // field value
		ft := t.Field(i) // field type

		if ft.Anonymous {
			// TODO:  We will deal with it later 
			continue
		}
		if !fv.CanInterface() {
			continue
		}

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

		str, ok := readFieldVal(&fv, tg)
		if !ok {
			continue
		}
		if str == "" && tg.Has("omitempty") {
			continue
		}

		//  Write  KV  value 
		kv.Set(tg.Name(), str)
	}

	return kv, nil
}

verification

Let's write a simple one Go test Function to verify :

func TestMarshal(t *testing.T) {
	type Pet struct {
		OwnerID string `url:"owner_id,omitempty" json:"ownerID"`
		Name    string `url:",omitempty"`
		Sex     int
	}

	p := Pet{
		OwnerID: "tencent",
		Name:    "Penguin",
		Sex:     1,
	}

	s, _ := Marshal(&p)
	t.Log(string(s))
}

//  Output 
// Name=Penguin&Sex=1&owner_id=tencent

You can see , In the output content, correctly follow tag Configuration in , Serialize the fields in the structure into a byte stream .

next step

OK, If the reader's needs , Just serialize the basic data types ( character string 、 Boolean value 、 Numbers ), So far ,marshal Function can be regarded as completed .

But do readers remember what we left in this article TODO term , This is what we need to deal with in the next article . The code of this article can also be found in Github Found on the , The codes in this phase correspond to Commit b2db350.

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 2: Structure member traversal 》

Release date :2021-06-29

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

原网站

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