Skip to content

4.8 序列化 ​

JSON 编解码 ​

基础用法 ​

go
package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    City string `json:"city,omitempty"`  // 空值时省略
}

func main() {
    // 结构体转 JSON
    p := Person{Name: "Alice", Age: 25, City: "Beijing"}
    data, _ := json.Marshal(p)
    fmt.Println(string(data))  // {"name":"Alice","age":25,"city":"Beijing"}

    // 格式化输出
    data2, _ := json.MarshalIndent(p, "", "  ")
    fmt.Println(string(data2))
    /*
    {
      "name": "Alice",
      "age": 25,
      "city": "Beijing"
    }
    */

    // JSON 转结构体
    var p2 Person
    json.Unmarshal(data, &p2)
    fmt.Println(p2.Name)  // Alice

    // JSON 转 map
    var m map[string]interface{}
    json.Unmarshal(data, &m)
    fmt.Println(m["name"])  // Alice
}

JSON 标签详解 ​

go
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID       int    `json:"id"`              // 字段名
    Name     string `json:"name"`            // 字段名
    Email    string `json:"email,omitempty"` // 空值省略
    Password string `json:"-"`               // 忽略此字段
    Token    string `json:"token,omitempty"`
    Role     string `json:"role,string"`     // 数字转字符串
}

func main() {
    user := User{
        ID:       1,
        Name:     "Alice",
        Email:    "",
        Password: "secret",
        Role:     "admin",
    }

    data, _ := json.MarshalIndent(user, "", "  ")
    fmt.Println(string(data))
    /*
    {
      "id": 1,
      "name": "Alice",
      "role": "admin"
    }
    */
}

自定义 Marshal/Unmarshal ​

go
package main

import (
    "encoding/json"
    "fmt"
    "time"
)

// 自定义时间类型
type MyTime time.Time

func (mt MyTime) MarshalJSON() ([]byte, error) {
    s := time.Time(mt).Format("2006-01-02 15:04:05")
    return json.Marshal(s)
}

func (mt *MyTime) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    t, err := time.Parse("2006-01-02 15:04:05", s)
    if err != nil {
        return err
    }
    *mt = MyTime(t)
    return nil
}

type Event struct {
    Name string  `json:"name"`
    Time MyTime  `json:"time"`
}

func main() {
    event := Event{
        Name: "会议",
        Time: MyTime(time.Now()),
    }

    data, _ := json.MarshalIndent(event, "", "  ")
    fmt.Println(string(data))

    // 解析
    var event2 Event
    json.Unmarshal(data, &event2)
    fmt.Println(event2.Name)
}

JSON 解码器高级用法 ​

go
package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

func main() {
    // 使用 Decoder 流式解码
    jsonData := `{"name":"Alice"}{"name":"Bob"}{"name":"Charlie"}`
    decoder := json.NewDecoder(strings.NewReader(jsonData))

    for decoder.More() {
        var m map[string]interface{}
        decoder.Decode(&m)
        fmt.Println(m["name"])
    }

    // UseNumber: 数字不转 float64,保留精度
    jsonData2 := `{"price": 19.99, "count": 100}`
    var m map[string]interface{}
    decoder2 := json.NewDecoder(strings.NewReader(jsonData2))
    decoder2.UseNumber()
    decoder2.Decode(&m)
    fmt.Println(m["price"])  // 19.99 (json.Number 类型)
    fmt.Println(m["count"])  // 100 (json.Number 类型)

    // DisallowUnknownFields: 不允许未知字段
    type Person struct {
        Name string `json:"name"`
    }
    jsonData3 := `{"name":"Alice","extra":"field"}`
    var p Person
    decoder3 := json.NewDecoder(strings.NewReader(jsonData3))
    decoder3.DisallowUnknownFields()
    if err := decoder3.Decode(&p); err != nil {
        fmt.Println("错误:", err)  // 会报错
    }
}

XML 编解码 ​

基础用法 ​

go
package main

import (
    "encoding/xml"
    "fmt"
)

type Person struct {
    XMLName xml.Name `xml:"person"`
    ID      int      `xml:"id,attr"`      // 属性
    Name    string   `xml:"name"`         // 子元素
    Age     int      `xml:"age"`          // 子元素
    City    string   `xml:"address>city"` // 嵌套元素
}

func main() {
    p := Person{
        ID:   1,
        Name: "Alice",
        Age:  25,
        City: "Beijing",
    }

    // 结构体转 XML
    data, _ := xml.MarshalIndent(p, "", "  ")
    fmt.Println(string(data))
    /*
    <person id="1">
      <name>Alice</name>
      <age>25</age>
      <address>
        <city>Beijing</city>
      </address>
    </person>
    */

    // XML 转结构体
    xmlData := `<person id="1"><name>Alice</name><age>25</age><address><city>Beijing</city></address></person>`
    var p2 Person
    xml.Unmarshal([]byte(xmlData), &p2)
    fmt.Println(p2.Name)
}

XML 标签详解 ​

go
package main

import "encoding/xml"

type Product struct {
    XMLName     xml.Name `xml:"product"`
    ID          string   `xml:"id,attr"`           // 属性
    Name        string   `xml:"name"`              // 子元素
    Price       float64  `xml:"price"`             // 子元素
    Description string   `xml:",chardata"`         // 字符数据
    Tags        []string `xml:"tags>tag"`          // 重复元素
    Metadata    Metadata `xml:"metadata"`          // 嵌套
}

type Metadata struct {
    Created string `xml:"created"`
    Updated string `xml:"updated"`
}

YAML 支持 ​

使用 gopkg.in/yaml.v3 ​

go
package main

import (
    "fmt"
    "gopkg.in/yaml.v3"
)

type Config struct {
    Server   ServerConfig `yaml:"server"`
    Database DatabaseConfig `yaml:"database"`
    Logging  LoggingConfig  `yaml:"logging"`
}

type ServerConfig struct {
    Host string `yaml:"host"`
    Port int    `yaml:"port"`
}

type DatabaseConfig struct {
    Driver   string `yaml:"driver"`
    Host     string `yaml:"host"`
    Port     int    `yaml:"port"`
    Username string `yaml:"username"`
    Password string `yaml:"password"`
}

type LoggingConfig struct {
    Level  string `yaml:"level"`
    Format string `yaml:"format"`
}

func main() {
    yamlData := `
server:
  host: localhost
  port: 8080
database:
  driver: postgres
  host: localhost
  port: 5432
  username: admin
  password: secret
logging:
  level: info
  format: json
`

    var config Config
    yaml.Unmarshal([]byte(yamlData), &config)
    fmt.Println(config.Server.Host)

    // 转 YAML
    data, _ := yaml.Marshal(&config)
    fmt.Println(string(data))
}

读取 YAML 配置文件 ​

go
package main

import (
    "os"
    "gopkg.in/yaml.v3"
)

func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err
    }

    var config Config
    if err := yaml.Unmarshal(data, &config); err != nil {
        return nil, err
    }

    return &config, nil
}

Protocol Buffers ​

定义 .proto 文件 ​

protobuf
// person.proto
syntax = "proto3";
package main;

message Person {
    int32 id = 1;
    string name = 2;
    string email = 3;
    repeated string phones = 4;
}

message AddressBook {
    repeated Person people = 1;
}

生成 Go 代码 ​

bash
# 安装 protoc 编译器
# 从 https://github.com/protocolbuffers/protobuf/releases 下载

# 安装 Go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 生成代码
protoc --go_out=. --go_opt=paths=source_relative person.proto

使用生成的代码 ​

go
package main

import (
    "fmt"
    "google.golang.org/protobuf/proto"
    pb "path/to/your/proto"
)

func main() {
    // 创建消息
    person := &pb.Person{
        Id:     1,
        Name:   "Alice",
        Email:  "alice@example.com",
        Phones: []string{"123456", "789012"},
    }

    // 序列化
    data, _ := proto.Marshal(person)
    fmt.Println("序列化大小:", len(data))

    // 反序列化
    var person2 pb.Person
    proto.Unmarshal(data, &person2)
    fmt.Println("Name:", person2.Name)
}

Gob 序列化 ​

基础用法 ​

go
package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{Name: "Alice", Age: 25}

    // 编码
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    encoder.Encode(p)

    fmt.Println("编码后大小:", buf.Len())

    // 解码
    var p2 Person
    decoder := gob.NewDecoder(&buf)
    decoder.Decode(&p2)

    fmt.Println(p2.Name)
}

Gob 传输 ​

go
package main

import (
    "encoding/gob"
    "net"
)

// 服务端
func server() {
    ln, _ := net.Listen("tcp", ":8080")
    conn, _ := ln.Accept()
    defer conn.Close()

    encoder := gob.NewEncoder(conn)
    encoder.Encode(Person{Name: "Alice", Age: 25})
}

// 客户端
func client() {
    conn, _ := net.Dial("tcp", "localhost:8080")
    defer conn.Close()

    decoder := gob.NewDecoder(conn)
    var p Person
    decoder.Decode(&p)
}

序列化性能对比 ​

go
package main

import (
    "bytes"
    "encoding/gob"
    "encoding/json"
    "encoding/xml"
    "testing"

    "gopkg.in/yaml.v3"
)

type TestData struct {
    ID    int    `json:"id" xml:"id" yaml:"id"`
    Name  string `json:"name" xml:"name" yaml:"name"`
    Email string `json:"email" xml:"email" yaml:"email"`
    Count int    `json:"count" xml:"count" yaml:"count"`
}

func BenchmarkJSONMarshal(b *testing.B) {
    data := TestData{ID: 1, Name: "Test", Email: "test@example.com", Count: 100}
    for i := 0; i < b.N; i++ {
        json.Marshal(data)
    }
}

func BenchmarkJSONUnmarshal(b *testing.B) {
    jsonStr := `{"id":1,"name":"Test","email":"test@example.com","count":100}`
    for i := 0; i < b.N; i++ {
        var data TestData
        json.Unmarshal([]byte(jsonStr), &data)
    }
}

func BenchmarkXMLMarshal(b *testing.B) {
    data := TestData{ID: 1, Name: "Test", Email: "test@example.com", Count: 100}
    for i := 0; i < b.N; i++ {
        xml.Marshal(data)
    }
}

func BenchmarkYAMLMarshal(b *testing.B) {
    data := TestData{ID: 1, Name: "Test", Email: "test@example.com", Count: 100}
    for i := 0; i < b.N; i++ {
        yaml.Marshal(data)
    }
}

func BenchmarkGobEncode(b *testing.B) {
    data := TestData{ID: 1, Name: "Test", Email: "test@example.com", Count: 100}
    for i := 0; i < b.N; i++ {
        var buf bytes.Buffer
        enc := gob.NewEncoder(&buf)
        enc.Encode(data)
    }
}

/*
典型性能结果:
- 速度:Gob > JSON > YAML > XML
- 大小:Gob < JSON < YAML < XML
- 可读性:YAML > JSON > XML > Gob
- 通用性:JSON > YAML > XML > Gob

使用建议:
- Web API: JSON (通用,跨语言)
- 配置文件:YAML (人类可读)
- 内部服务:Gob/Protobuf (高效,Go 专用)
- 文档交换:XML (标准,结构化)
*/

序列化检查清单 ​

[ ] JSON 字段使用合适的标签 (omitempty, -)
[ ] 敏感数据添加 json:"-" 标签
[ ] 时间类型使用自定义格式
[ ] 大数字使用 json.Number 保持精度
[ ] 解析未知字段时使用 DisallowUnknownFields
[ ] 配置文件使用 YAML
[ ] 高性能场景使用 Protobuf/Gob
[ ] 跨语言 API 使用 JSON
[ ] 验证输入数据大小避免 DoS
[ ] 添加适当的错误处理
[ ] 考虑版本兼容性