
Go 的 encoding/json 默认忽略非导出字段(如 _id),但可通过结构体标签 json:"_id" 将导出字段映射为以下划线开头的 JSON 键,从而满足 MongoDB 等外部系统对 _id 字段的要求。
go 的 `encoding/json` 默认忽略非导出字段(如 `_id`),但可通过结构体标签 `json:"_id"` 将导出字段映射为以下划线开头的 json 键,从而满足 mongodb 等外部系统对 `_id` 字段的要求。
在 Go 中,JSON 序列化严格遵循“导出性规则”:只有首字母大写的字段(即导出字段)才能被 json.Marshal 处理;以下划线开头的字段(如 _id)若未导出(例如 _id int),则会被完全忽略——这导致写入 MongoDB 时无法使用自定义 _id,数据库将自动生成 ObjectId,进而破坏 ID 一致性与反序列化可预测性。
根本解法不是让非导出字段参与序列化,而是利用结构体标签(struct tag)实现语义映射:定义一个导出字段(如 ID int),再通过 `json:"_id"` 显式指定其在 JSON 中的键名。该方式零侵入、无需自定义 MarshalJSON 方法,且完全兼容标准库。
以下是一个典型示例:
package main
import (
"encoding/json"
"fmt"
"os"
)
type User struct {
ID string `json:"_id"` // 导出字段 ID → JSON 键 "_id"
Username string `json:"username"`
Email string `json:"email,omitempty"`
}
func main() {
user := User{
ID: "user_123",
Username: "alice",
Email: "alice@example.com",
}
data, err := json.Marshal(user)
if err != nil {
fmt.Println("marshal error:", err)
return
}
os.Stdout.Write(data) // 输出: {"_id":"user_123","username":"alice","email":"alice@example.com"}
}✅ 关键要点说明:
- 字段名 ID 必须首字母大写(导出),否则 json 包无法访问;
- `json:"_id"` 标签控制序列化后的键名,支持任意合法 JSON 字符串(包括 _id、_createdAt 等);
- 若需同时支持 JSON 序列化与 MongoDB BSON 序列化(如使用 go.mongodb.org/mongo-driver/bson),可叠加多标签:
ID string `json:"_id" bson:"_id"`
- 避免使用 json:"-"(显式忽略)或空标签 json:""(使用字段名小写),以免意外覆盖预期行为。
⚠️ 注意事项:
- 不要尝试将字段命名为 _ID 或 _id 并设为导出(如 _ID int)——虽然语法合法,但严重违反 Go 命名惯例,降低可读性与可维护性;
- 若结构体需双向(JSON ↔ 结构体)精确映射,确保反序列化时也使用相同标签,且目标字段类型兼容(例如 MongoDB 的 _id 可为 string、ObjectID 等,需按实际驱动要求匹配);
- 对于嵌套结构或切片中的文档,同样适用该标签机制,无需额外处理。
综上,通过合理设计导出字段名并配合 json 标签,即可优雅、安全、标准化地生成符合外部系统(如 MongoDB)契约的 JSON 输出,无需重写序列化逻辑,是 Go 生态中推荐的最佳实践。










