我们使用Golang开发后台时,经常会和JSON打交道,使用JSON主要是使用官方的encoding/json库,这里介绍一些和json库相关的使用技巧

关于性能

传说中老有人说官方库性能不算很好,有更好的选择,但我们基本的场景都感觉不到,如果真的项目进展到需要做性能优化的时候,再考虑其他库不迟。

多用Encoder和Decoder

我们一般会使用json.Unmarshall和json.Marshall作为主要的结构体解析和串行方法外,但在后端代码中,我更愿意用json.NewEncoder和json.NewDecoder从reader里读取。这样我就不需要使用ioutil.ReadAll来把body全部读取出来了,直接从http.Request的Body里读取。写的时候也可以直接写入到ResponseWriter就好了。

http.HandleFunc("/check", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Access-Control-Allow-Origin", origin)
    json.NewEncoder(w).Encode(map[string]interface{}{
      "success":  true,
      "identity": "baijiafan_test_daemon",
    })
})
注意默认值

因为主要是json和结构体之间的相互转换,所以golang的一个问题是要小心默认值,比如一个bool类型的值,即便实际没有传这个值,也会被赋值为false,所以有很多人喜欢用*bool这样的指针,这时候,如果并没有传这个值,Unmarshall的时候会直接设置为null,就可以进一步通过指针来判断到底是没传还是传的false。

我个人更喜欢调整定义,比如需求说这个接口增加一个是否拦截(Deny)的参数,那我们最好定义Allow而不是Deny,或者定义为NotDeny,这时候,默认的false就是默认不允许,进一步开发就不会出问题,但是如果你定义成Deny或者NotAllow,那默认false是不是就变成放行了?这时候就容易出问题。

老是有null怎么办?

活用tag里面的omitempty,这时,false,0,"", null这些值串行的时候就都会被隐藏,要不然默认情况下,传一堆null给前端,通常会被骂。

本来就是JSON内容的[]byte怎么放进大的对象里?

我们一般没办法把byte数组原封不动的串行进json字符串,比如有时,我在结构体里会有Response属性,我希望把第三方调用的结果直接放到这个属性里,这时直接放[]byte会把字符串给base64化,如果确实需要bytes作为一个子节点,要用json.RawMessage类型

func TestJson1(t *testing.T) {
	var str = `{
		"value": "1"
	}`
	var temp struct {
		Value []byte
	}
	temp.Value = []byte(str)
	bts, err := json.Marshal(temp)
	fmt.Println(string(bts), err)
}
输出:
{"Value":"ewoJCSJ2YWx1ZSI6ICIxIgoJfQ=="}

func TestJson1(t *testing.T) {
	var str = `{
		"value": "1"
	}`
	var temp struct {
		Value json.RawMessage
	}
	temp.Value = []byte(str)
	bts, err := json.Marshal(temp)
	fmt.Println(string(bts), err)
}
输出
{"Value":{"value":"1"}} //成功!
前端的数字当字符串传了怎么办?

如果要从一个字符串属性中读取数字,可以使用string tag

Int64String int64 `json:",string"`

这个tag就是专门处理这种数字放进string的问题的

{
   "no": "3"
}

如果再BT点,我们有多个调用方客户,他们也许会传"3",也可能是3怎么办?这时可以用json.Number,例子:

func TestJson(t *testing.T) {
	var str = `{
		"value": "1"
	}`
	var temp struct {
		Value json.Number `json:"value"`
	}
	err := json.Unmarshal([]byte(str), &temp)
	fmt.Println(temp, err)
	i, e := temp.Value.Int64()
	fmt.Println(i, e)
	f, e := temp.Value.Float64()
	fmt.Println(f, e)
}
输出:
{1} <nil>
1 <nil>
1 <nil>

这个写法当str是下面形态时,一样有效

{
  "value": 1
}

此时,json.Number同样会获得到结果,json.Number也可以用来指代一些既可能是Int又可能是float的数据,这时我们可以使用Int64()来尝试获得int数据。

interface怎么Unmarshall

当结构体里有interface的时候比较麻烦,我们不得不在目标结构体中实现UnmarshalJson和MarshalJson两个函数用于自定义转化过程,一般我们还需要定义一个临时类型来做中间转换,或者直接使用struct定义的,类似下面例子中的temp

type Detail interface {
}

Type DetailA struct {
}

Type DetailB struct {
}

type Result struct {
    Type int
    Detail Detail
}

func(r *Result)UnmarshalJson(data []byte)error{
    var temp struct {
       Type int
       Detail json.RawMessage
    }
    json.Unmarshall(data,&temp)
    r.Type = temp.Type
    //先拿出Type,再根据Type做Detail的Unmarshall
    //Detail此时就需要根据定义选择真正的struct进行Unmarshall
    switch temp.Type {
      case 1:
         var dA DetailA
         json.Unmarshall(temp.Detail,&dA)
         r.Detail = dA
      ....... //其他情况
    }
}