
在使用 go 语言的 `encoding/json` 包进行 json 序列化时,如果自定义结构体(struct)的字段以小写字母开头,`json.marshal` 将无法正确导出这些字段的值,导致输出的 json 对象为空。本文将深入探讨 go 语言的字段可见性规则,并通过示例代码演示如何通过将结构体字段的首字母大写来使其可被导出,从而确保 `json.marshal` 能够成功序列化结构体的完整内容。
Go 语言中 json.Marshal 的常见陷阱
Go 语言的 encoding/json 包提供了一套强大且灵活的机制,用于在 Go 值和 JSON 格式之间进行转换。然而,初学者在使用 json.Marshal 将自定义结构体序列化为 JSON 字符串时,常常会遇到一个令人困惑的问题:尽管结构体实例中明明包含了数据,但序列化后的 JSON 字符串却显示为空对象(例如 {"key":{}})。这通常不是因为 json.Marshal 本身存在错误,而是源于 Go 语言中一个核心概念——字段的可见性(或称导出性)。
问题现象分析
考虑以下 Go 代码示例,其中定义了一个名为 node 的结构体,其所有字段都以小写字母开头:
package main
import (
"encoding/json"
"fmt"
)
type node struct {
value string
expiry float64
settime float64
}
func main() {
var x = make(map[string]node)
x["hello"] = node{value: "world", expiry: 1, settime: 2}
x["foo"] = node{value: "bar", expiry: 1, settime: 2}
a, err := json.Marshal(x)
if err != nil {
fmt.Println("Error marshalling:", err)
return
}
fmt.Println(string(a))
}运行上述代码,预期会得到包含 value、expiry 和 settime 字段的 JSON 输出。然而,实际输出却是:
{"foo":{},"hello":{}}可以看到,"foo" 和 "hello" 键对应的值都是空的 JSON 对象 {},结构体 node 内部的字段值并未被序列化。
根本原因:Go 语言的导出规则
在 Go 语言中,标识符(包括结构体字段、函数、方法、变量等)的可见性是由其首字母的大小写决定的:
- 大写字母开头的标识符:表示该标识符是导出(exported)的,可以在包外部访问。
- 小写字母开头的标识符:表示该标识符是非导出(unexported)的,只能在其所在的包内部访问。
encoding/json 包在执行 json.Marshal 操作时,只会序列化结构体中导出的字段。对于非导出字段,它会将其忽略。在上述示例中,node 结构体中的 value、expiry 和 settime 字段都以小写字母开头,因此它们是非导出的,json.Marshal 无法访问并序列化它们。
解决方案:导出结构体字段
解决这个问题的关键在于遵循 Go 语言的导出规则,将需要序列化到 JSON 的结构体字段的首字母改为大写。
修改结构体定义
将 node 结构体修改为 Node(通常结构体名称也建议首字母大写以表示其可导出),并将其字段的首字母也改为大写:
package main
import (
"encoding/json"
"fmt"
)
// Node 结构体,字段首字母大写,表示可导出
type Node struct {
Value string
Expiry float64
Settime float64
}
func main() {
var x = make(map[string]Node) // 注意这里也需要使用 Node 类型
x["hello"] = Node{Value: "world", Expiry: 1, Settime: 2}
x["foo"] = Node{Value: "bar", Expiry: 1, Settime: 2}
a, err := json.Marshal(x)
if err != nil {
fmt.Println("Error marshalling:", err)
return
}
fmt.Println(string(a))
}预期输出
现在,当运行修改后的代码时,json.Marshal 将能够正确识别并序列化 Node 结构体的所有导出字段,输出结果将是:
{"foo":{"Value":"bar","Expiry":1,"Settime":2},"hello":{"Value":"world","Expiry":1,"Settime":2}}这正是我们期望的完整 JSON 序列化结果。
总结与注意事项
- 导出规则是核心:在 Go 语言中,任何需要被 encoding/json 包序列化的结构体字段都必须是导出的(即首字母大写)。
- 不仅仅是 json.Marshal:Go 语言的导出规则适用于整个 Go 生态系统,不仅限于 encoding/json。它影响着包之间的可见性、接口实现等多个方面。
-
json 标签的用途:如果希望 JSON 字段名与 Go 结构体字段名不同,或者需要对字段进行其他控制(例如忽略空值),可以使用结构体字段标签(json:"field_name,omitempty")。但即使使用了标签,字段本身也必须是导出的。例如:
type User struct { Name string `json:"user_name"` // 导出字段,JSON名为 user_name Age int `json:"age,omitempty"` // 导出字段,如果为零值则在JSON中忽略 } - json.Unmarshal 同样适用:在将 JSON 数据反序列化到 Go 结构体时(使用 json.Unmarshal),目标结构体的字段也必须是导出的,才能接收对应的 JSON 值。
- 错误处理:在实际开发中,务必对 json.Marshal 和 json.Unmarshal 的返回错误进行妥善处理,以便及时发现并解决序列化/反序列化过程中可能出现的问题。
理解并正确应用 Go 语言的导出规则是有效使用 encoding/json 包的关键。通过确保结构体字段的首字母大写,可以避免常见的序列化问题,并确保 Go 应用程序与 JSON 数据之间的数据交换顺畅无误。










