You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 09:54:22