Service Computing (5)-Development of Object Serialization Support Package

Course tasks

  1. Refer to the official encoding/json package Marshal function to format the structure data into a json character stream

    Must export func JsonMarshal(v interface{}) ([]byte, error)

    You can refer to or even copy the original code

    Support field tags (Tag), tags meet mytag:"你自己的定义"

    No third-party packages allowed

  2. The package must include the following:

    Generated Chinese api documentation

    There is a good Readme file, including a simple use case

    Each go file must have a corresponding test file

design description

Create a new package json , a new file json.go , which implements functions func JsonMarshal(v interface{}) ([]byte, error).

In the previous study, we know that in the go language, capitalized initials indicate that they are accessible outside, and non-capitalized ones are inaccessible. In the realization of the JsonMarshal function, we inevitably have to call other functions to nest dolls , we can encapsulate other unnecessary functions, that is, start with a lowercase letter.

Through the official source code encoding/json/encode.go , we can know the general implementation process of the Marshal function, using the reflect package, which is also called in our subsequent implementations.

The implementation in the official document is relatively complicated, because many factors are considered, and we did n't consider so much if we implemented a simplified and usable version.

The first is the function func JsonMarshal(v interface{}) ([]byte, error).

func JsonMarshal(v interface{
    
    }) ([]byte, error) {
    
    
	b, err := marshal(v)
	if err != nil {
    
    
		return nil, err
	}
	return b, nil
}

This is achieved by calling a private function marshal in the package .

Then to the function func marshal(v interface{}) ([]byte, error).

func marshal(v interface{
    
    }) ([]byte, error) {
    
    
	if v == nil {
    
    
		return []byte("null"), nil
	}
	s := reflect.ValueOf(v)
	typeOfS := s.Type()
	switch typeOfS.Kind() {
    
    
	case reflect.Slice, reflect.Array :
		return sliceMarshal(v)
	case reflect.Struct :
		return structMarshal(v)
	case reflect.Map :
		return mapMarshal(v)
	case reflect.Ptr :
		return marshal(s.Elem().Interface())
	case reflect.String :
		return []byte("\"" + s.String() + "\""), nil
	default :
		return []byte(fmt.Sprintf("%v", s.Interface())), nil
	}
}

First, we need to determine the type. In addition to basic types such as string and int, which can be returned directly, the Ptr type needs to take the address, and everything else must be handed over to the corresponding function implementation.

Look first func sliceMarshal(v interface{}) ([]byte, error).

func sliceMarshal(v interface{
    
    }) ([]byte, error) {
    
    
	var b strings.Builder
	b.WriteByte('[')
	s := reflect.ValueOf(v)
	for i := 0; i < s.Len(); i++ {
    
    
		f := s.Index(i)
		if i > 0 {
    
    
			b.WriteByte(',')
		}		
		tempB, e := marshal(f.Interface())
		if e != nil {
    
    
			return nil, e
		}
		b.Write(tempB)
	}
	b.WriteByte(']')
	return []byte(b.String()), nil
}

Traverse the array/shard once, and then call the marshal function to parse the corresponding value again . Matryoshka

Againfunc structMarshal(v interface{}) ([]byte, error)

func structMarshal(v interface{
    
    }) ([]byte, error) {
    
    
	var b strings.Builder
	b.WriteByte('{')
	s := reflect.ValueOf(v)
	typeOfS := s.Type()
	for i := 0; i < s.NumField(); i++ {
    
    
		f := s.Field(i)
		if i > 0 {
    
    
			b.WriteByte(',')
		}
		tag := typeOfS.Field(i).Tag.Get("mytag")
		if tag == "" {
    
    
			b.WriteString(fmt.Sprintf("\"%s\":", typeOfS.Field(i).Name))
		} else {
    
    
			b.WriteString(fmt.Sprintf("\"%s\":", tag))
		}		
		tempB, e := marshal(f.Interface())
		if e != nil {
    
    
			return nil, e
		}
		b.Write(tempB)
	}
	b.WriteByte('}')
	return []byte(b.String()), nil
}

The parsing of the struct type is relatively complicated, but it is not too much, it just needs to print the variable name. If there is a tag, it will print the corresponding tag. The corresponding value is also parsed by calling the marshal function again . Matryoshka

The last is func mapMarshal(v interface{}) ([]byte, error).

func mapMarshal(v interface{
    
    }) ([]byte, error) {
    
    
	var b strings.Builder
	b.WriteByte('{')
	s := reflect.ValueOf(v)
	it := s.MapRange()
	first := true
	for it.Next() {
    
    
		if first {
    
    
			first = false
		} else {
    
    
			b.WriteByte(',')
		}
		b.WriteString(fmt.Sprintf("\"%v\":", it.Key()))
		tempB, e := marshal(it.Value().Interface())
		if e != nil {
    
    
			return nil, e
		}
		b.Write(tempB)
	}
	b.WriteByte('}')
	return []byte(b.String()), nil
}

It is still to traverse the Map again, print the Key and Value, and the doll

At this point, the function func JsonMarshal(v interface{}) ([]byte, error)is complete.

test

Writing a test for each function in json.go doesn't seem to be of much use, because each of them calls each other dolls.

type Monitor struct {
    
    
	ID		int
	Name		string
}

type Group struct {
    
    
	ID		int
	Name		string
	Members	[]string
	Nums		[3]int
	M		Monitor			`mytag:"GG"`
	Manage	map[int][]int
}

func ExampleSliceMarshal() {
    
    
	b, err := sliceMarshal([]int{
    
    1,2,3})
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	//Output:[1,2,3]
}

func ExampleStructMarshal() {
    
    
	group := Group{
    
    
		ID:1,
		Name:"Reds",
		Members:[]string{
    
    "Crimson", "Red", "Ruby", "Maroon"},
		Nums:[3]int{
    
    1,2,3},
		M:Monitor{
    
    18666,"yzdl"},
	}
	group.Manage= make(map[int][]int)
	group.Manage[2] = []int{
    
    1,2,3}
	b, err := structMarshal(group)
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	//Output:{"ID":1,"Name":"Reds","Members":["Crimson","Red","Ruby","Maroon"],"Nums":[1,2,3],"GG":{"ID":18666,"Name":"yzdl"},"Manage":{"2":[1,2,3]}}
}

func ExampleMapMarshal() {
    
    
	M := make(map[int][]int)
	M[2] = []int{
    
    1,2,3}
	b, err := mapMarshal(M)
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	//Output:{"2":[1,2,3]}
}

func ExampleMarshal() {
    
    
	b, err := marshal(123)
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	//Output:123
}

func ExampleJsonMarshal() {
    
    
	group := Group{
    
    
		ID:1,
		Name:"Reds",
		Members:[]string{
    
    "Crimson", "Red", "Ruby", "Maroon"},
		Nums:[3]int{
    
    1,2,3},
		M:Monitor{
    
    18666,"yzdl"},
	}
	group.Manage= make(map[int][]int)
	group.Manage[2] = []int{
    
    1,2,3}
	b, err := JsonMarshal(&group)
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	//Output:{"ID":1,"Name":"Reds","Members":["Crimson","Red","Ruby","Maroon"],"Nums":[1,2,3],"GG":{"ID":18666,"Name":"yzdl"},"Manage":{"2":[1,2,3]}}
}

Basically, each type of value is executed by marshal.

Insert picture description here

Then execute the instructions go build github.com/github-user/HHH/json. Other programs can import "github.com/github-user/HHH/json"call the functions in this package.

Write a test.go and have a try.

package main

import (
	"fmt"
	"github.com/github-user/HHH/json"
)

type Group struct {
    
    
	ID		int
	Name		string
}


type ColorGroup struct {
    
    
	ID		int
	Name		string
	Colors	[]string
	Nums		[3]int
	G		Group			`mytag:"GG"`
	M		map[int][]int
}

func main() {
    
    
	group := ColorGroup{
    
    
		ID:1,
		Name:"Reds",
		Colors:[]string{
    
    "Crimson", "Red", "Ruby", "Maroon"},
		Nums:[3]int{
    
    1,2,3},
		G:Group{
    
    1,"123"},
	}
	group.M  = make(map[int][]int)
	group.M[2] = []int{
    
    1,2,3}
	b, err := json.JsonMarshal(group)
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	b, err = json.JsonMarshal(&group)
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
	b, err = json.JsonMarshal("123456")
	if err != nil {
    
    
	    fmt.Println("error:", err)
	}
	fmt.Println(string(b))
}

The instruction is executed go run test.goand the program runs normally.

Insert picture description here

Generate API documentation

Just like the previous job , the execution godoccan access the API through the corresponding path.

Insert picture description here

Assignment submission

My complete code

Guess you like

Origin blog.csdn.net/qq_43278234/article/details/109278731