Go使用fsnotify监听JSON文件时读空、首次启动不加载问题
Go fsnotify监听JSON文件问题修复方案
问题1:ReadFile返回解析结果为空
核心原因有4个:
- 错误全部被忽略:
os.Open失败时仅打印日志就继续执行,ioutil.ReadAll、json.Unmarshal的返回错误直接用_丢弃,完全无法感知执行异常。你提供的测试JSON末尾存在多余的尾逗号(最后一个news数组结束后多了一个逗号),标准库JSON解析器不支持这种非法格式,会直接反序列化失败,因为没有捕获错误所以完全没发现。 - 空指针风险:如果
os.Open执行失败返回nil文件句柄,后续defer jsonFile.Close()会直接触发空指针panic。 - 变量名不一致:监听路径用的变量是
file_json,调用ReadFile时传的是file_name,如果两个变量值不统一会直接读错文件。 - 版本兼容问题:
ioutil.ReadAll在Go 1.16及以上版本已被废弃,应替换为io.ReadAll。
修复后的ReadFile实现:
type Information struct { Info []Info `json:"info"` } type Info struct { Type string `json:"type"` News []New `json:"news"` } type New struct { Name string `json:"name"` Read bool `json:"read"` } func ReadFile(fileName string) (*Information, error) { jsonFile, err := os.Open(fileName) if err != nil { return nil, fmt.Errorf("open file failed: %w", err) } defer jsonFile.Close() byteValue, err := io.ReadAll(jsonFile) if err != nil { return nil, fmt.Errorf("read file failed: %w", err) } var infor Information if err := json.Unmarshal(byteValue, &infor); err != nil { return nil, fmt.Errorf("parse json failed: %w", err) } return &infor, nil }
同时需要修正测试JSON的非法格式,删除多余尾逗号:
{ "info": [ { "type": "general", "news": [ { "name": "abc", "read": true }, { "name": "def", "read": true } ] }, { "type": "confidential", "news": [ { "name": "xxx", "read": false }, { "name": "yyy", "read": false } ] } ] }
问题2:启动时不加载初始文件内容
核心原因是事件循环只会在收到文件写入事件时才触发读取逻辑,程序启动后没有主动执行一次文件读取。另外fsnotify在很多系统/编辑器场景下单次保存会触发多次Write事件,容易导致重复读取,需要加简单防抖。
修复后的主逻辑代码:
const configFilePath = "./your_json_file.json" // 统一定义文件路径,避免变量名写错 func main() { watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatalf("init watcher failed: %v", err) } defer watcher.Close() if err := watcher.Add(configFilePath); err != nil { log.Fatalf("watch file failed: %v", err) } // 启动后主动加载一次初始内容 loadAndPrintConfig(configFilePath) var lastLoadTime time.Time for { select { case ev, ok := <-watcher.Events: if !ok { return } // 过滤重复Write事件,100毫秒内的重复触发只执行一次 if ev.Op&fsnotify.Write == fsnotify.Write && time.Since(lastLoadTime) > 100*time.Millisecond { loadAndPrintConfig(configFilePath) lastLoadTime = time.Now() } case err, ok := <-watcher.Errors: if !ok { return } log.Printf("watcher error: %v", err) } } } // 抽离公共读取打印逻辑,初始加载和变更触发复用 func loadAndPrintConfig(path string) { data, err := ReadFile(path) if err != nil { log.Printf("load config error: %v", err) return } fmt.Println("INFORMATION ABOUT FILE:") for _, info := range data.Info { fmt.Printf("Info type: %s\n", info.Type) for _, news := range info.News { fmt.Printf("News Name: %s\n", news.Name) fmt.Printf("News Read: %t\n", news.Read) // 直接用%t占位符格式化布尔值,无需额外转换 } } }
补充注意:部分编辑器(Vim、VS Code等)保存文件时采用临时文件替换原文件的逻辑,这种场景下会触发
fsnotify.Rename/fsnotify.Remove事件,原文件监听会失效,需要额外监听这两类事件,重新将目标文件加入监听列表才能持续捕获变更。
内容的提问来源于stack exchange,提问作者X T
相关产品推荐
相关产品推荐

