go json转换实践中遇到的坑

2019-11-10 12:22:15王振洲

根据输出可以判断,go 的 json 包使用的是 RFC3339 标准中定义的格式。接下来测试一下 json.Unmarshal 方法所支持的日期时间格式。

dateStr := "2018-10-12"

var person Person
jsonStr := fmt.Sprintf("{"name":"Wang Wu", "Birth": "%s"}", dateStr)
json.Unmarshal([]byte(jsonStr), &person)

fmt.Println(person.Birth) // 0001-01-01 00:00:00 +0000 UTC

对于形如 2018-10-12 的字符串,json 包并没有成功将其解析,接下来我们把 time 包中支持的所有格式都试一下。

经过试验,发现 json.Unmarshal 方法只支持 RFC3339 和 RFC3339Nano 两种格式的转换。还有一个需要注意的地方,使用 time.Now() 生成的时间是带有一个 Monotonic Time 的,经过 json.Marshal 转换时候,由于 RFC3339 规范里没有存放 Monotonic Time 的位置,会丢掉这一部分。

对于字段为空的处理

json 包对于空值的处理是一个非常容易出错的地方,看下面代码。

type Person struct {
 Name  string
 Age  int64
 Birth time.Time
 Children []Person
}

func main() {
 person := Person{}

 jsonBytes, _ := json.Marshal(person)
 fmt.Println(string(jsonBytes)) // {"Name":"","Age":0,"Birth":"0001-01-01T00:00:00Z","Children":null}
}

当 struct 中的字段没有值时,使用 json.Marshal 方法并不会自动忽略这些字段,而是根据字段的类型输出了他们的默认空值,这往往和我们的预期不一致,json 包提供了对字段的控制手段,我们可以为字段增加 omitempty tag,这个 tag 会在字段值为零值(int 和 float 类型零值是 0,string 类型零值是 "",对象类型零值是 nil)时,忽略该字段。

type PersonAllowEmpty struct {
 Name  string    `json:",omitempty"`
 Age  int64    `json:",omitempty"`
 Birth time.Time   `json:",omitempty"`
 Children []PersonAllowEmpty `json:",omitempty"`
}

func main() {
 person := PersonAllowEmpty{}
 jsonBytes, _ := json.Marshal(person)
 fmt.Println(string(jsonBytes)) // {"Birth":"0001-01-01T00:00:00Z"}
}

可以看到,这次输出的 json 中只有 Birth 字段了,string、int、对象类型的字段,都因为没有赋值,默认是零值,所以被忽略,对于日期时间类型,由于不可以设置为零值,也就是 0000-00-00 00:00:00,不会被忽略。

需要注意这样的情况:如果一个人的年龄是 0 (对于刚出生的婴儿,这个值是合理的),刚好是 int 字段的零值,在添加 omitempty tag 的情况下,年龄字段会被忽略。

如果想要某一个字段在任何情况下都被 json 包忽略,需要使用如下的写法。

type Person struct {
 Name  string `json:"-"`
 Age  int64 `json:"-"`
 Birth time.Time `json:"-"`
 Children []string `json:"-"`
}

func main() {
 birth, _ := time.Parse(time.RFC3339, "1988-12-02T15:04:27+08:00")
 person := Person{
  Name: "Wang Wu",
  Age: 30,
  Birth: birth,
  Children: []string{},
 }

 jsonBytes, _ := json.Marshal(person)
 fmt.Println(string(jsonBytes)) // {}
}