我们使用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
....... //其他情况
}
}