应优先使用 json 而非 gob;因 bson 原始字节不可跨语言可靠读取,需转为语言中立格式,json 最稳妥;若仅 go 内闭环且重性能才选 gob;注意用 bson.marshaljson 处理 objectid 等特殊类型。

用 gob 还是 json?别直接序列化 BSON 原始字节
Go 里没有“把 BSON 写入文件”的标准函数,因为 bson.M 或 bson.D 是内存结构,不是可直接写磁盘的格式。常见误区是想把 bson.Marshal() 返回的 []byte 当作通用二进制数据直接保存——它确实能写进去,但后续无法跨语言或跨版本可靠读取(BSON 规范版本、字段顺序、类型精度都可能出问题)。真正可维护的做法是转成语言中立、人类可读/调试的格式,json 是最稳妥选择;若只在 Go 生态内闭环且追求性能,才考虑 gob。
推荐:用 json.Marshal 转换后写文件
BSON 类型(如 ObjectId、DateTime、Binary)不能被原生 json.Marshal 处理,会报错或丢数据。必须先用 bson.MarshalJSON()(来自
)转成 JSON 兼容结构:
<pre class="brush:php;toolbar:false;">doc := bson.M{"_id": bson.ObjectIdHex("60a8b1f2e9b3d42a1c8e9f01"), "name": "Alice", "created": time.Now()}
data, err := bson.MarshalJSON(doc)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("data.json", data, 0644)
if err != nil {
log.Fatal(err)
}
bson.MarshalJSON() 会把 <code>ObjectId输出为{"$oid": "..."},DateTime输出为{"$date": "..."},保留语义- 读取时用
bson.UnmarshalJSON()可完整还原原始 BSON 结构,不丢失类型信息 - 生成的文件是合法 JSON,可用
jq、VS Code 等工具直接查看和编辑
如果坚持写纯 BSON 二进制文件(仅限特定场景)
仅当你要对接其他 BSON 工具链(如 mongodump 生成的 .bson 文件),或做底层协议调试时才这么做。注意:bson.Marshal() 输出不含文档长度前缀,标准 BSON 文件每条记录需以 4 字节长度开头(little-endian),否则多数工具无法识别:
doc := bson.M{"name": "Bob"}
data, _ := bson.Marshal(doc)
// 手动补长度头(4 字节小端)
length := make([]byte, 4)
binary.LittleEndian.PutUint32(length, uint32(len(data)+4))
fullData := append(length, data...)
os.WriteFile("data.bson", fullData, 0644)
- 漏掉长度头 → 用
bsondump会报invalid length - 多条记录要拼接?每条都得独立加长度头,不能直接
append原始bson.Marshal()结果 - Go 官方驱动不提供“写 BSON 文件”封装,全靠手动处理,容易出错
别忽略时间字段的时区和精度问题
BSON 的 DateTime 是毫秒级 Unix 时间戳(UTC),但 Go 的 time.Time 默认带本地时区。直接存 time.Now() 后用 bson.MarshalJSON(),输出的 $date 字符串会是 UTC 时间,但如果你期望本地时区显示,得提前转换:
t := time.Now().In(time.Local) // 或 t := time.Now().UTC()
doc := bson.M{"ts": t}
data, _ := bson.MarshalJSON(doc) // 输出 $date 值已按 In() 后的时区计算
- 未显式调用
In()→MarshalJSON仍用 UTC,但可能和业务日志时区不一致 - 微秒/纳秒精度会被截断为毫秒(BSON 规范限制),有高精度需求得改用字符串字段存储
bson.MarshalJSON + os.WriteFile 是最不容易翻车的方式。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











