课程任务
-
参考官方 encoding/json 包 Marshal 函数,将结构数据格式化为 json 字符流
必须导出
func JsonMarshal(v interface{}) ([]byte, error)
可以参考、甚至复制原来的代码
支持字段的标签(Tag),标签满足
mytag:"你自己的定义"
不允许使用第三方包
-
包必须包括以下内容:
生成的中文 api 文档
有较好的 Readme 文件,包括一个简单的使用案例
每个go文件必须有对应的测试文件
设计说明
新建一个包 json,新建文件 json.go,里面实现函数func JsonMarshal(v interface{}) ([]byte, error)
。
在前面的学习里,我们知道,在go语言里面,首字母大写的表示在外面是可访问的,非大写的就是不可访问的。在JsonMarshal函数的实现里面,我们不可避免地要调用其他函数套娃再套娃,可以把其他不必要的函数封装起来,也就是以小写字母开头。
通过官方源码encoding/json/encode.go ,我们可以知道Marshal函数的大致实现过程,用的是reflect包,在我们后续的实现中同样也是调用这个包。
官方文档里面的实现相对比较复杂,因为考虑了诸多因素,而我们这实现一个简化能用版的话就没考虑这么多了。
首先是函数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
}
通过调用一个包内私有函数marshal来实现。
再到函数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
}
}
先是要判断类型,除了string、int等基本类型可以直接返回以外,Ptr类型则需要取地址,其他的都要下交给对应的函数实现。
先看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
}
将数组/分片遍历一遍,对应的值就再次调用marshal函数来解析。套娃
再到func 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
}
对于struct类型的解析相对来说有一丢丢复杂,但也差不太多,只是需要打印变量名。有Tag的情况下就打印对应Tag。所对应的值同样也是再次调用marshal函数来解析。套娃
最后是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
}
依旧是将Map遍历一遍,打印Key跟Value,套娃
至此,函数func JsonMarshal(v interface{}) ([]byte, error)
就完成了。
测试
对json.go里面的每一个函数都编写一个测试,好像也没什么大作用,因为每个都是互相调用套娃的。
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]}}
}
基本上就是每种类型的值都让marshal执行一遍。
然后执行指令go build github.com/github-user/HHH/json
。其他程序就能够通过import "github.com/github-user/HHH/json"
来调用这个包里面的函数。
编写一个test.go试一试。
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))
}
执行指令go run test.go
,程序正常运行。
生成API文档
就像上一次作业一样,执行godoc
就能够通过相应路径访问API了。