
本文讲解如何避免生成非法的多行 json(即多个独立 json 对象),通过将所有字段统一封装进一个 json 数组对象中,并提供安全、可扩展的读写方案。
本文讲解如何避免生成非法的多行 json(即多个独立 json 对象),通过将所有字段统一封装进一个 json 数组对象中,并提供安全、可扩展的读写方案。
在 Go 中处理 JSON 文件时,一个常见误区是:每次追加新数据都直接 json.Marshal 后写入文件末尾——这会导致文件内容变成多个彼此独立的 JSON 对象(如 {"a":1}{"b":2}),而标准 JSON 规范只允许一个顶层值(如单个对象 {} 或数组 [])。因此,当你尝试用 json.Unmarshal 解析该文件时,会遇到 invalid character '{' after top-level value 错误。
正确的做法是:始终将所有数据维护在一个 JSON 数组中。例如,合法结构应为:
[
{"Date":"2017-03-20 10:46:48","Description":"new"},
{"Date":"2017-03-20 10:46:51","Description":"new .go"}
]
而非两行独立对象。
✅ 推荐实现方式(安全、清晰、符合 Go 惯例)
type Reminder struct {
Date string `json:"Date"`
Description string `json:"Description"`
}
func appendReminder(filename string, r Reminder) error {
// 1. 读取现有数据(若文件不存在则初始化为空切片)
data, err := os.ReadFile(filename)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("read file: %w", err)
}
var reminders []Reminder
if len(data) > 0 {
if err := json.Unmarshal(data, &reminders); err != nil {
return fmt.Errorf("parse existing JSON: %w", err)
}
}
// 2. 追加新条目
reminders = append(reminders, r)
// 3. 重新序列化并完整写入(覆盖原文件)
newData, err := json.MarshalIndent(reminders, "", " ")
if err != nil {
return fmt.Errorf("marshal JSON: %w", err)
}
if err := os.WriteFile(filename, newData, 0644); err != nil {
return fmt.Errorf("write file: %w", err)
}
return nil
}
调用示例:
err := appendReminder("rem.json", Reminder{
Date: time.Now().Format("2006-01-02 15:04:05"),
Description: "Meeting with team",
})
if err != nil {
log.Fatal(err)
}
⚠️ 注意事项与优化建议
-
原子性与并发安全:上述实现非并发安全。若需多 goroutine 写入,请加
sync.Mutex或使用文件锁(如syscall.Flock)。 -
大文件性能优化:当
rem.json达到 MB 级别且写入频繁时,全量读写开销增大。此时可考虑: -
错误处理不可省略:示例中已包含关键错误链式包装(
%w),便于调试定位问题源头。 -
JSON 格式可读性:使用
json.MarshalIndent(..., "", " ")生成带缩进的格式化输出,方便人工查看与 Git diff。
✅ 验证与遍历数据
加载并遍历所有提醒项非常直观:
data, _ := os.ReadFile("rem.json")
var reminders []Reminder
json.Unmarshal(data, &reminders)
for i, r := range reminders {
fmt.Printf("[%d] %s: %s\n", i, r.Date, r.Description)
}
总结:永远让 JSON 文件保持单一合法顶层值(推荐数组),避免“逐行 JSON”陷阱;以“读—改—写”模式维护数据一致性,兼顾正确性与可维护性。











