Go 中结构体无法序列化为 JSON 的原因及解决方案

go 的 `json.marshal` 只能序列化首字母大写的导出字段,小写开头的结构体字段(如 `local_address`)属于未导出成员,会被忽略,导致输出空对象 `{}`。解决方法是将字段名首字母大写并添加 json 标签,或自定义 `marshaljson` 方法。

在 Go 语言中,JSON 序列化依赖于 字段的可见性(exported/unexported),而非字段是否被显式标记。encoding/json 包运行在独立包内,它无法访问非导出(即小写开头)的结构体字段——这是 Go 的基础封装机制决定的。因此,原始代码中所有字段(如 local_address, local_port 等)均为小写开头,json.Marshal 对它们“视而不见”,最终只得到一个空 JSON 对象 {}。

✅ 正确做法:将字段名首字母大写,并推荐添加 json 标签以精确控制键名与行为:

type Configitem struct {
    LocalAddress string `json:"local_address"`
    LocalPort    int    `json:"local_port"`
    Method       string `json:"method"`
    Password     string `json:"password"`
    Server       string `json:"server"`
    ServerPort   string `json:"server_port"`
    Timeout      int    `json:"timeout"`
}

type GuiConfig struct {
    Configs []*Configitem `json:"configs"`
    Index   int           `json:"index"`
}

修改后,完整可运行示例:

package main

import (
    "encoding/json"
    "fmt"
)

type Configitem struct {
    LocalAddress string `json:"local_address"`
    LocalPort    int    `json:"local_port"`
    Method       string `json:"method"`
    Password     string `json:"password"`
    Server       string `json:"server"`
    ServerPort   string `json:"server_port"`
    Timeout      int    `json:"timeout"`
}

type GuiConfig struct {
    Configs []*Configitem `json:"configs"`
    Index   int           `json:"index"`
}

func main() {
    item1 := &Configitem{
        LocalAddress: "eouoeu",
        LocalPort:    111,
        Method:       "eoeoue",
        Password:     "ouoeu",
        Server:       "oeuoeu",
        ServerPort:   "qoeueo",
        Timeout:      3333,
    }

    config1 := &GuiConfig{
        Index:   1,
        Configs: []*Configitem{item1},
    }

    data, err := json.Marshal(config1)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))
    // 输出:{"configs":[{"local_address":"eouoeu","local_port":111,"method":"eoeoue","password":"ouoeu","server":"oeuoeu","server_port":"qoeueo","timeout":3333}],"index":1}
}

⚠️ 注意事项:

  • 字段名必须以大写字母开头(如 LocalAddress),否则即使加了 json 标签也无效;
  • json 标签中的值(如 "local_address")定义了 JSON 输出的键名,支持别名、忽略("-")和条件省略(",omitempty");
  • 若因设计需要保留字段私有性(不导出),可实现 MarshalJSON() ([]byte, error) 方法来自定义序列化逻辑,但会增加维护成本,一般不推荐;
  • 嵌套结构体同理:所有层级中参与 JSON 序列化的字段都必须是导出字段。

总结:Go 的 JSON 序列化严格遵循“导出即可见”原则。养成首字母大写 + 显式 json 标签的习惯,是编写健壮、可维护序列化代码的关键实践。