安全修改JSON文件必须走“读取→内存修改→写临时文件→原子重命名”流程:首次运行需用os.IsNotExist判断并初始化,默认配置;动态字段用封装的get/set函数防panic;写回时临时文件须同分区,close后rename确保原子性。

直接用 json.Unmarshal + json.MarshalIndent 覆盖写入是错的——首次运行文件不存在会 panic,并发写或崩溃时可能清空整个配置;动态字段多、结构不确定时硬套 struct 容易类型断言 panic;真正安全的修改必须走「读取→内存修改→写临时文件→原子重命名」流程。
os.ReadFile 读不到文件就 panic?先检查 os.IsNotExist
Go 不会自动创建配置文件,os.ReadFile("config.json") 在首次运行时必然失败。不能只写 if err != nil { panic(err) }。
- 用
if errors.Is(err, os.ErrNotExist)(Go 1.13+)或os.IsNotExist(err)判断是否为“文件不存在” - 如果是,跳过解析,直接初始化默认配置(比如空 struct 或预设 map)
- 其他错误(如权限不足、路径含非法字符)才该报错退出
- Windows 下注意路径中中文或空格,建议用
filepath.Join("conf", "config.json")拼接
用 map[string]interface{} 修改嵌套字段太容易 panic?封装 get/set 工具函数
写 data["server"].(map[string]interface{})["port"].(float64) 这类链式断言,只要中间某层类型不对或 key 不存在,立刻 crash。别手写,封装可复用的 get 和 set:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
get(data interface{}, path ...interface{}) interface{}支持混合路径,如get(cfg, "database", "timeout", 0)取数组首项里的 timeout 字段 -
set(value interface{}, data interface{}, path ...interface{})同样支持 string 键和 int 索引,自动处理中间 map / slice 创建 - 内部用
type switch安全下钻,遇到 nil 或类型不匹配时返回 nil 而非 panic - 避免把整个 JSON 当成
map[string]interface{}处理——如果已知部分结构(如顶层固定字段),优先定义 struct,只对动态子段用 map
写回文件为什么总丢数据?必须用临时文件 + os.Rename
os.WriteFile("config.json", data, 0644) 是覆盖写,写到一半进程崩溃,原文件就没了。正确做法是:
立即学习“go语言免费学习笔记(深入)”;
- 临时文件路径必须与目标在同一磁盘分区,否则
os.Rename会失败(底层是复制+删除):用tempFile, err := os.Create(filepath.Join(filepath.Dir(configPath), "config.json.tmp")) - 写完立即
tempFile.Close(),再调os.Rename(tempFile.Name(), configPath) - 记得设权限:临时文件也用
0644,否则重命名后可能因权限丢失导致后续读取失败 - Linux 下若目标文件被其他进程 mmap 或正在 tail -f,
Rename仍能成功,旧句柄继续读原内容,无竞态
json.MarshalIndent 后字段顺序乱了?别依赖键序,用 json.RawMessage 延迟解析
标准库序列化不保证字段顺序,json.MarshalIndent 输出的 key 顺序取决于 struct 字段声明顺序或 map 遍历顺序(Go 1.12+ map 遍历随机化)。如果你的配置工具或前端依赖字段顺序(比如某些 YAML-to-JSON 转换器):
- 不要用
map[string]interface{}存储需保序的配置段——改用json.RawMessage字段,把原始 JSON 字节块原样保留 - 例如:
type Config struct { Header json.RawMessage `json:"header"` Settings map[string]interface{} `json:"settings"` },只解析 settings,header 原样透传 - 修改 header 内容时,先
json.Unmarshal到临时 map,改完再json.Marshal回json.RawMessage - 保序不是 JSON 规范要求,但现实里有些老旧系统真会按 key 顺序解析,这点容易被忽略

















