
本文讲解 go 中处理 json 文件时避免“invalid character '{' after top-level value”错误的核心方法:将多个 json 对象统一封装为数组结构,并通过读-改-写流程安全追加新记录。
本文讲解 go 中处理 json 文件时避免“invalid character '{' after top-level value”错误的核心方法:将多个 json 对象统一封装为数组结构,并通过读-改-写流程安全追加新记录。
在 Go 中直接以多行独立 JSON 对象(即 JSON Lines 格式)写入文件,会导致后续 json.Unmarshal 无法解析——因为标准 JSON 解析器期望一个单一、合法的顶层值(如对象 {} 或数组 []),而连续的 {...}{...} 是非法的。你遇到的 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"}
]这样,整个文件内容就是一个合法的 JSON 值(数组),可被 json.Unmarshal 完整解析。
✅ 推荐实现方式(安全、可维护)
以下是一个完整、健壮的追加逻辑示例(基于 os 和 encoding/json):
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. 序列化并覆盖写入(确保原子性:先写临时文件,再原子替换更佳)
out, err := json.MarshalIndent(reminders, "", " ")
if err != nil {
return fmt.Errorf("marshal JSON: %w", err)
}
if err := os.WriteFile(filename, append(out, '\n'), 0644); err != nil {
return fmt.Errorf("write file: %w", err)
}
return nil
}调用示例:
err := AppendReminder("rem.json", Reminder{
Date: time.Now().Format("2006-03-02 15:04:05"),
Description: "Meeting with team",
})
if err != nil {
log.Fatal(err)
}⚠️ 注意事项
-
不要直接追加字符串(如
file.WriteString(",{...}]")):易破坏 JSON 结构(遗漏逗号、括号不匹配、编码问题),且非线程/进程安全; -
大文件优化:若记录量极大(万级+),可考虑使用数据库(SQLite)或流式解析库(如
jsoniter+ 自定义 reader),但绝大多数场景下“全量读写”足够高效且语义清晰; -
并发安全:上述函数非并发安全;如需多 goroutine 写入,请加
sync.Mutex或使用文件锁(如syscall.Flock); -
错误处理不可省略:尤其
json.Unmarshal和os.WriteFile的错误必须显式检查,避免静默失败。
✅ 总结
将 JSON 数据组织为数组([]T)而非多行对象(JSON Lines),是 Go 中实现可解析、可扩展、可维护 JSON 文件存储的基石。配合“读取 → 修改切片 → 重新序列化 → 覆盖写入”的模式,既能保证格式合法性,又无需引入额外依赖。务必避免手动拼接 JSON 字符串——让 json.Marshal 承担结构与转义责任,才是地道的 Go 实践。


















