In the last article we learned how to use reflect to check the type of a parameter. In this paper, we have obtained a structure type, so we need to explore the structure inside the structure and its corresponding value.

Structure member iteration

In the last article, our marshal function currently looks like this:

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

So at this point, we’ve got a struct reflect.Value variable. Next, let’s add a few more lines of code that look 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.umField () kV = url.values {} // Iterate each field for I := 0; i < numField; i++ { fv := v.Field(i) // field value ft := t.Field(i) // field type // ...... } return kv, nil }

The variable t is a reflect.type, which represents the Type of the current variable, and its function numField (), which in the case of struct Type variables, represents the number of all member fields under that variable.

Member resolution process

For each field in the structure to iterate, see fv := v.ield (I) and ft := t.ield (I). The fv variable is reflect.Value, which is familiar to the reader from the last article. But the variable TV is of type reflect.structfield, which is a new type. It represents an attribute of the field type in the structure.

For a structure member, we need to check other properties besides the field header type. This requires several parameters of the fv and ft variables, as shown below:

Anonymous members

In the Go structure, anonymous members are supported. There are several points to consider regarding the handling of anonymous members. We’ll skip it here, but we’ll talk more about it later, so the code looks like this:

func marshalToValues(in interface{}) (kv url.Values, err error) { // ...... For I := 0; for I := 0; i < numField; I ++ {fv := v.ffield (I) // field value ft := t.ffield (I) // field type if ft. Continue} //...... } return kv, nil }

Non-exportable member

In the Go structure, public (exportable) members begin with an uppercase letter, while private (non-exportable) members begin with a lowercase letter. According to the GO convention, private members are not handled during marshal/unmarshal operations, so these members should be filtered out.

There is one exception: anonymous members themselves may not be exportable, and this needs to be handled differently. So we put the anonymous member processing logic first. So the code at this point is rewritten as follows:

func marshalToValues(in interface{}) (kv url.Values, err error) { // ...... For I := 0; for I := 0; i < numField; I ++ {fv := v.ffield (I) // field value ft := t.ffield (I) // field type if ft. Continue} if! Fv.CanInterface() {// Exportable, continue with the fv variable's CanInterface function} //...... } return kv, nil }

Go the tag parsing

We know that in many of Go’s marshal/unmarshal functions, the mapping of the structure variables and the key values of the byte stream is done by the tag in the structure, known as the tag. Take the following definition for example:

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

This tag is used to associate the ownerID in the byte stream with the ownerID variable. The following omitEmpty is used as an additional tag specification to indicate that the value of a field is not encoded if the value is equal to the null value.

For the Name field, since the tag is not explicitly specified, its key is mapped to the same Name as the variable Name by default.

Therefore, since we are writing our own marshal/unmarshal functions, it is obvious that we should also follow this pattern of involvement. Let’s write a short piece of code to parse the tag information of this field, with * reflect.structField as the argument, and implement the following function:

  • If the specified tag configuration is not null, there are two cases:

    • All right, so the data before the comma is the key name
    • There is no content before the comma. In this case, the name of the field is used as the tag
  • If the specified Tag configuration does not exist, the name of the field is used as the tag
  • Supports getting additional parameters
Type Tags []string func readTag(ft * reflect.structField, Tag string) tags {Tg := ft.tag.get (Tag) // If the Tag configuration is not empty, If TG! = "" { res := strings.Split(tg, ",") if res[0] ! = "" { return res } return append(tags{ft.Name}, res[1:]...) } // If the tag is empty, return tags{ft.name}} // Name represents the first field defined by the current tag This field must be func (TG Tags) Name() string {return TG [0]} // Has Has determined whether the current tag is configured with some additional parameter values, 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 }

The above configuration covers several TAG cases in the new PET type.

At this point, all we need to do is add one more filter branch and we can move on. When the tag configuration value is equal to -, as per GO conventions, this means to ignore the changed field:

func marshalToValues(in interface{}) (kv url.Values, err error) { // ...... For I := 0; for I := 0; i < numField; I ++ {fv := v.ffield (I) // field value ft := t.ffield (I) // field type if ft. Continue} if! Fv.canInterface () {fv.canInterface () { Continue} g := readTag(&ft,) "Url") if tg. The Name () = = "-" continue} {/ / - said to ignore the current field / /... } return kv, nil }

Structure member values are read

After the previous filtering, we are now able to obtain the information of each valid structure field that we need to process, and the next step is to obtain the value of each structure member.

For this step we use the fv variable, which is a reflect.value type. Due to different data types, the value method is different.

Please review Reflect.Kind type here. At this stage, we will deal with the following data types for the time being:

  • string
  • The integer
  • floating-point
  • The Boolean

As for the other types are more complex, we will be further explained in the following article.

Needless to say, this little piece of code isn’t very long, like this:

func readFieldVal(v *reflect.Value, tag tags) (s string, ok bool) { switch v.Type().Kind() { default: Return "", false // Unsupported variable type, return 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 the value function for various types:

type The value function note
string v.String()
Unsigned integers v.Uint() No matter what the bit width, uniform accessuint64type
Signed integers v.Int() No matter what the bit width, uniform accessint64type
reflect.Bool v.Bool()
Floating point Numbers v.Float() Unified accessfloat64type

So, pretty soon, the body of our iteration function is complete:

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 fields under the structure kv = url.values {} // Iterate each field for I := 0; i < numField; I ++ {fv := v.ield (I) // field value ft := t.ield (I) // field type if ft. 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.Set(Tg.Name (), STR)} return kV, nil}

validation

Let’s write a simple Go Test function to verify this:

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(String (s)).Log(String (s))

As you can see, the output correctly serializes the fields in the structure to byte streams as configured in Tag.

The next step

OK, if the reader’s requirement is to serialize only the basic data types (string, Boolean, number), then the marshal function is done at this point.

But if you remember that we left some TODO items in this article, that’s the functionality we’ll be dealing with in the next article. The code for this article is also available on GitHub. The code for this phase corresponds to Commit B2DB350.

Other Articles Recommended

  • 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 analyze the structure of Go – Step 1: Check the type of parameters

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.

3. Reflect your Reflections with a step-by-step approach to resolving Go’s structures – Step 2: Structure member traversal

Post Date: 2021-06-29

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