Go结构体存文件不丢字段需满足四点:字段首字母大写(导出)、启动时加载数据、修改后立即写回、并发操作加锁;JSON用tag控制key大小写,gob要求类型完全一致。

Go结构体怎么存到文件里才不会丢字段
直接用 json.Marshal 或 gob.Encoder 存结构体,常见问题是字段全变成零值,或者读出来字段为空——根本原因是 Go 的结构体字段必须首字母大写(即导出字段)才能被序列化。
比如你写了个 type Todo struct { text string } ,text 小写,json.Marshal 后就是 {};改成 Text string 才行。
- 切片字段也一样:用
[]string没问题,但别用items []string(小写字段名) - 如果想保留小写字段名输出(比如 JSON key 要小写),加 tag:
Text string `json:"text"` -
gob不支持 struct tag,且要求类型在 encode/decode 两端完全一致(包括包路径),适合本地持久化;json更灵活,但不保存 nil 切片和空字符串的语义差异
TODO List用切片存数据,为什么一重启就清空了
因为内存里的 []Todo 每次程序启动都是新变量,没读文件就直接用了空切片。持久化不是“写了文件”就完事,关键在「启动时加载」和「修改后及时写回」两个动作都得做。
典型错误是只在添加 todo 时写文件,但没在 main() 开头调用读取函数;或者读取失败(比如文件不存在)时没给默认值,导致切片保持 nil,后续 append panic。
立即学习“go语言免费学习笔记(深入)”;
- 启动时用
os.ReadFile读文件,json.Unmarshal到*[]Todo,失败则初始化空切片 - 每次增删改后立刻
json.Marshal+os.WriteFile,别攒着等退出再写(进程崩溃就丢了) - 文件路径别硬编码成
"todos.json",用os.UserHomeDir()拼个.todo.json更稳妥
多个 goroutine 并发操作 TODO 切片会出什么问题
如果没加锁,append 可能覆盖彼此、漏掉条目,甚至触发 slice 底层数组重分配时的竞态——panic 报错通常是 fatal error: concurrent map writes(虽然你没用 map,但底层 runtime 对 slice 扩容的检测有时也走类似路径)。
简单命令行工具没必要上 sync.RWMutex,但至少得包一层带锁的容器:
- 定义
type TodoStore struct { todos []Todo; mu sync.Mutex } - 所有读写方法(
Add、Delete、All)开头调s.mu.Lock(),结尾defer s.mu.Unlock() - 别把
[]Todo直接暴露出去,All()返回副本:copy(dst, s.todos),否则外部仍可能并发改底层数组
JSON 文件手动编辑后 Go 程序读不出来
不是语法错,大概率是字段类型对不上。比如你在文件里把 "done": true 改成 "done": "true"(字符串),Go 结构体里 Done bool 就解不出,json.Unmarshal 静默失败并留零值。
另一个高频坑是时间字段:Go 的 time.Time 默认序列化为 RFC3339 字符串(如 "2024-05-20T14:23:11Z"),手输成 "2024-05-20" 就解析失败。
- 调试时先打印
err:别只写if err != nil { return },至少log.Printf("load err: %v", err) - 用
json.RawMessage延迟解析可疑字段,或加自定义UnmarshalJSON方法处理兼容格式 - 生产环境别依赖手动编辑 JSON,加个
--export命令导出可读格式,--import导入校验后再写入
字段可见性、启动加载时机、并发安全、类型严格性——这四个点漏掉任一个,TODO List 看似跑起来,实际随时丢数据或崩给你看。










