📊 一、JSON 与 Go 类型对应关系
理解 JSON 类型与 Go 类型的映射是处理 JSON 的基础:
JSON 类型 | Go 类型 | 说明 |
---|---|---|
对象 (object) | struct , map[string]T |
键值对集合 |
数组 (array) | []T (切片), [n]T (数组) |
值的有序集合,通常使用切片更灵活 |
字符串 (string) | string |
|
数字 (number) | int , float64 等 |
注意 :JSON 中的数字默认解码为 float64 |
布尔 (boolean) | bool |
|
null | nil |
🛠️ 二、基本操作:编码与解码
Go 使用标准库 encoding/json
进行 JSON 处理。
-
编码 (序列化,Marshal) :使用
json.Marshal
将 Go 数据结构转换为 JSON 字节切片 ([]byte
)。gopackage main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` // 通过标签指定JSON字段名 Age int `json:"age"` } func main() { p := Person{Name: "Alice", Age: 30} jsonBytes, err := json.Marshal(p) if err != nil { panic(err) } fmt.Println(string(jsonBytes)) // 输出:{"name":"Alice","age":30} }
-
解码 (反序列化,Unmarshal) :使用
json.Unmarshal
将 JSON 字节切片解析回 Go 数据结构。gojsonStr := `{"name":"Bob","age":25}` var p2 Person err = json.Unmarshal([]byte(jsonStr), &p2) // 注意传递指针 if err != nil { panic(err) } fmt.Printf("%+v\n", p2) // 输出:{Name:Bob Age:25}
-
格式化输出 :使用
json.MarshalIndent
生成带缩进的格式化 JSON,便于阅读。gojsonBytes, err := json.MarshalIndent(p, "", " ") // 前缀为空,缩进为两个空格 if err != nil { panic(err) } fmt.Println(string(jsonBytes)) /* 输出: { "name": "Alice", "age": 30 } */
🔧 三、结构体标签(Struct Tags)的运用
结构体标签是控制 JSON 序列化和反序列化行为的关键。
-
自定义字段名 :使用
json:"field_name"
指定 JSON 字段名。gotype Product struct { ProductID int `json:"id"` // JSON中字段名为 "id" Name string `json:"name"` // JSON中字段名为 "name" }
-
忽略字段:
- 永久忽略 :使用
json:"-"
,该字段不会参与 JSON 序列化。
gotype User struct { Name string `json:"name"` Password string `json:"-"` // 序列化时忽略密码字段 }
- 条件忽略(空值) :使用
omitempty
,当字段为零值时(如空字符串、零值、nil指针等),序列化时省略该字段。
gotype User struct { Name string `json:"name"` Email string `json:"email,omitempty"` // 如果Email为空,则序列化时不包含此字段 Age int `json:"age,omitempty"` // 如果Age为0,则序列化时不包含此字段 }
- 永久忽略 :使用
-
字符串形式的数字 :有时需要将数字类型以字符串形式序列化,或从字符串形式的数字反序列化,可以使用
,string
标签选项。gotype MyData struct { Num int `json:"num,string"` // 序列化为 {"num": "123"}, 从 {"num": "123"} 反序列化 }
注意 :此标签仅适用于
int
,float
,bool
等类型与字符串形式的互转,且 JSON 中对应的值必须是带引号的字符串。
🧩 四、处理未知结构或动态 JSON
当你无法预知 JSON 的确切结构时,有两种主要方法:
-
使用
map[string]interface{}
:适合快速访问,但需要类型断言,安全性较低。gojsonStr := `{"name":"Alice","age":30,"hobbies":["reading","traveling"]}` var data map[string]interface{} err := json.Unmarshal([]byte(jsonStr), &data) if err != nil { panic(err) } name := data["name"].(string) // 类型断言 age := data["age"].(float64) // JSON数字默认转为float64 hobbies := data["hobbies"].([]interface{}) fmt.Println(name, age, hobbies[0])
-
使用
json.RawMessage
:延迟解析,允许你先处理已知部分,再按需解析未知部分,更灵活安全。gotype Message struct { Name string `json:"name"` Extra json.RawMessage `json:"extra"` // 原始JSON字节,暂不解析 } var msg Message json.Unmarshal([]byte(jsonStr), &msg) // 之后根据条件再解析 Extra 字段
⏰ 五、处理时间类型(time.Time)
time.Time
类型默认序列化为 RFC 3339 格式的字符串。你也可以为 time.Time
定义自定义的 Marshaler
和 Unmarshaler
接口实现,以适应特定的时间格式。
go
type Event struct {
Name string `json:"name"`
Timestamp time.Time `json:"timestamp"` // 默认格式:2006-01-02T15:04:05Z07:00
}
event := Event{Name: "Party", Timestamp: time.Now()}
jsonData, _ := json.Marshal(event)
fmt.Println(string(jsonData)) // {"name":"Party","timestamp":"2023-10-01T10:00:00Z"}
🚀 六、流式编码与解码
处理大型 JSON 数据或 IO 流(如 HTTP 请求体、文件)时,使用 json.Encoder
和 json.Decoder
可以提高内存效率。
-
流式编码(写入)
gofile, err := os.Create("output.json") if err != nil { panic(err) } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") // 设置缩进 err = encoder.Encode(data) // 将数据直接编码并写入文件 if err != nil { panic(err) }
-
流式解码(读取)
gofile, err := os.Open("large_data.json") if err != nil { panic(err) } defer file.Close() decoder := json.NewDecoder(file) for { var item MyStruct if err := decoder.Decode(&item); err == io.EOF { break // 流结束 } else if err != nil { panic(err) } // 处理每一个 item fmt.Println(item) }
🧠 七、自定义序列化与反序列化
通过实现 json.Marshaler
和 json.Unmarshaler
接口,你可以完全控制特定类型的编码和解码逻辑。
go
type Status int
const (
Unknown Status = iota
Active
Inactive
)
// 实现 MarshalJSON,将 Status 转换为字符串
func (s Status) MarshalJSON() ([]byte, error) {
var str string
switch s {
case Active:
str = "active"
case Inactive:
str = "inactive"
default:
str = "unknown"
}
return json.Marshal(str)
}
// 实现 UnmarshalJSON,将字符串转换回 Status
func (s *Status) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
switch str {
case "active":
*s = Active
case "inactive":
*s = Inactive
default:
*s = Unknown
}
return nil
}
⚡ 八、性能优化建议
- 避免使用
interface{}
:尽可能使用明确类型的结构体,而不是map[string]interface{}
,这能减少反射开销并提高类型安全。 - 重用
json.Decoder
:在循环中解码多个 JSON 对象时,复用json.Decoder
实例。 - 预分配切片:如果你知道解码后数组的大致大小,预分配切片容量可以减少扩容带来的开销。
- **使用
json.RawMessage`` 延迟解析**:对于部分不需要立即处理的 JSON 数据,可以先存储为
json.RawMessage`,等到需要时再解析,从而减少不必要的解析开销。
💎 总结与建议
Go 语言的 encoding/json
库提供了强大而灵活的 JSON 处理能力。掌握从基础编码解码到高级技巧,能让你更自如地应对各种数据处理场景。记住,明确的结构体定义优于 map[string]interface{}
,善用结构体标签 能精细控制序列化行为,而 流式处理 则是处理大数据的利器。