9、encoding、json

9、encoding/json


序列化:Go 值 → JSON

使用 json.Marshal 或 json.MarshalIndent 将 Go 的数据类型转换为 JSON 字节切片。

基础用法

import "encoding/json"

type Person struct {
    Name string
    Age  int
}

p := Person{"Alice", 30}
data, _ := json.Marshal(p)
fmt.Println(string(data)) // {"Name":"Alice","Age":30}

// 带缩进
data, _ = json.MarshalIndent(p, "", "  ")
/*
{
  "Name": "Alice",
  "Age": 30
}
*/

序列化规则(无 tag)

  • 只有 导出字段(首字母大写) 才会被序列化。
  • JSON 键名默认与字段名相同(区分大小写)。

结构体标签 json:"..."

标签格式:json:"键名,选项"
选项可组合(如 json:"age,omitempty,string")。

选项 说明 示例
myName 自定义 JSON 键名 json:"user_name"
- 忽略该字段(不序列化也不反序列化) json:"-"
omitempty 零值时忽略字段(false, 0, 空指针, 空字符串等) json:"title,omitempty"
string 将数字转为 JSON 字符串(常用于 int64 精度问题) json:"id,string"
inline 将匿名字段(或标记了inline 的字段)的字段内联到父级,展平嵌套结构 inline

示例:

type User struct {
    ID       int64  `json:"id,string"`          // 输出为 "123"
    Name     string `json:"name"`
    Password string `json:"-"`                  // 忽略
    Age      int    `json:"age,omitempty"`       // 0 时不输出
    Email    string `json:"email,omitempty"`    // 空时不输出
}

输出:

{"id":"123","name":"Alice"}

嵌套结构体与匿名字段:

  • 匿名(嵌入)字段的字段会被提升,就像直接定义在外层一样(方法提升类似)。
  • 若外层有同名字段,会遮蔽内层;可通过显式路径访问内层字段。
  • 匿名字段也可带标签:json:"-" 会阻止提升。

反序列化:JSON → Go 值

使用 json.Unmarshal 将 JSON 字节切片解析到指定的变量(通常是指向结构体的指针)。

基础用法

data := []byte(`{"Name":"Bob","Age":25}`)
var p Person
err := json.Unmarshal(data, &p)

字段匹配规则

  • 首先查找 JSON 键与结构体标签中 json 名完全匹配的导出字段。
  • 若无标签,则按大小写不敏感匹配字段名。
  • 未匹配的 JSON 键被忽略;未匹配的结构体字段保持零值。
  • JSON 中的 null 会设置对应字段为 Go 的零值(比如 *int 设为 nil)。

类型兼容

  • JSON 数字可解析为 float64,但若目标字段是 int/int64 等,必须保证 JSON 数字是整数(无小数部分),否则报错。
  • string 标签的字段要求 JSON 中对应值为字符串,内部再转为数字。

处理未知字段

  • 使用 map[string]interface{} 接收所有键值,然后进行类型断言。
  • 利用 json.RawMessage 延迟解析部分字段。

示例:

var result map[string]interface{}
json.Unmarshal(data, &result)
name := result["name"].(string)

解析复杂嵌套 JSON

通过结构体精确建模

定义多层嵌套结构体,与 JSON 结构一一对应。

JSON 示例:

{
  "company": "Acme",
  "employees": [
    {
      "name": "Alice",
      "role": "engineer",
      "skills": ["Go", "Kubernetes"]
    },
    {
      "name": "Bob",
      "role": "manager",
      "skills": ["PM", "Agile"]
    }
  ],
  "address": {
    "city": "New York",
    "zip": "10001"
  }
}

Go 结构体定义:

type Company struct {
    Name      string     `json:"company"`
    Employees []Employee `json:"employees"`
    Address   Address    `json:"address"`
}

type Employee struct {
    Name   string   `json:"name"`
    Role   string   `json:"role"`
    Skills []string `json:"skills"`
}

type Address struct {
    City string `json:"city"`
    Zip  string `json:"zip"`
}

解析与输出:

var c Company
err := json.Unmarshal(jsonData, &c)
if err != nil { ... }
fmt.Printf("Company: %s, Employees: %d\n", c.Name, len(c.Employees))
// 遍历输出
for _, e := range c.Employees {
    fmt.Printf("- %s (%s): %v\n", e.Name, e.Role, e.Skills)
}

使用 json.RawMessage 处理不一致的结构

当某个字段的类型可能变化,或需要延迟解析时,使用 json.RawMessage 暂存原始 JSON 数据。

type Flexible struct {
    Kind string          `json:"kind"`
    Data json.RawMessage `json:"data"` // 先不解析
}

// 根据 Kind 再解析 Data 到不同结构体
var f Flexible
json.Unmarshal(input, &f)
if f.Kind == "typeA" {
    var a TypeA
    json.Unmarshal(f.Data, &a)
}

使用 interface{} 和类型断言解析未知结构

var anything interface{}
json.Unmarshal(data, &anything)
// 判断顶层是 map[string]interface{} 还是 []interface{}
m := anything.(map[string]interface{})
// 逐层断言...

但这种方法冗长且易错,推荐用结构体或 RawMessage。

核心要点速记

  1. 序列化:json.Marshal,只序列化导出字段,标签控制键名和行为。
  2. 反序列化:json.Unmarshal,字段匹配大小写不敏感,优先标签名。
  3. 标签选项:omitempty、-、string、重命名,组合使用。
  4. 嵌套 JSON:定义匹配的结构体层级,切片表示数组,结构体表示对象。
  5. 动态结构:json.RawMessage 延迟解析,interface{} 走类型断言。
posted @ 2026-09-23 16:32  爱喝奶茶的CC怪  阅读(13)  评论(0)    收藏  举报