服务计算(5)——对象序列化支持包开发

课程任务

  1. 参考官方 encoding/json 包 Marshal 函数,将结构数据格式化为 json 字符流

    必须导出 func JsonMarshal(v interface{}) ([]byte, error)

    可以参考、甚至复制原来的代码

    支持字段的标签(Tag),标签满足 mytag:"你自己的定义"

    不允许使用第三方包

  2. 包必须包括以下内容:

    生成的中文 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了。

在这里插入图片描述

作业提交

我的完整代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值