Article Directory
Course tasks
-
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
-
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.
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.go
and the program runs normally.
Generate API documentation
Just like the previous job , the execution godoc
can access the API through the corresponding path.