package main import ( "encoding/json" "fmt" "os" "path/filepath" ) // 字段需要导出(大写开头)才能由 encoding/json 读写。 // 反引号中的内容是结构体标签,不是字段的值。 type StudyRecord struct { Title string `json:"title"` Minutes int `json:"minutes"` Finished bool `json:"finished"` Tags []string `json:"tags"` Note string `json:"note,omitempty"` } // encodeRecord 将结构体转换为缩进后的 JSON 字节。 func encodeRecord(record StudyRecord) ([]byte, error) { return json.MarshalIndent(record, "", " ") } // decodeRecord 将 JSON 字节解析到新结构体。 // 本例只检查 JSON 语法和字段类型,不验证学习时长等业务规则。 func decodeRecord(data []byte) (StudyRecord, error) { var record StudyRecord if err := json.Unmarshal(data, &record); err != nil { return StudyRecord{}, err } return record, nil } func run() error { record := StudyRecord{ Title: "学习 Go JSON", Minutes: 30, Finished: true, Tags: []string{"Go", "JSON"}, // Note 为空字符串,会因 omitempty 在编码时省略。 } // 1. 编码:Go 值 -> JSON 字节。 data, err := encodeRecord(record) if err != nil { return fmt.Errorf("编码记录:%w", err) } fmt.Println("=== 编码为 JSON ===") fmt.Println(string(data)) // 2. 每次创建独立临时目录,不覆盖已有文件;运行结束保留供查看。 directory, err := os.MkdirTemp("", "go-lesson18-") if err != nil { return fmt.Errorf("创建临时目录:%w", err) } path := filepath.Join(directory, "record.json") // WriteFile 会覆盖已有文件;此处使用新建目录中的文件。 if err := os.WriteFile(path, data, 0600); err != nil { return fmt.Errorf("保存记录:%w", err) } fmt.Println("记录文件(运行后保留):", path) // 3. 读取文件,解码:JSON 字节 -> Go 值。 saved, err := os.ReadFile(path) if err != nil { return fmt.Errorf("读取记录:%w", err) } decoded, err := decodeRecord(saved) if err != nil { return fmt.Errorf("解码记录:%w", err) } fmt.Println("\n=== 读取并解码 ===") fmt.Printf("标题:%s,时长:%d 分钟,完成:%t,标签:%v\n", decoded.Title, decoded.Minutes, decoded.Finished, decoded.Tags) // 4. 非法 JSON 会返回错误;这里只演示,不让它中断整个程序。 _, err = decodeRecord([]byte(`{"minutes": "不是数字"}`)) if err != nil { fmt.Println("\n=== 错误示例 ===") fmt.Println("字段类型不匹配,解码失败") } return nil } func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, "程序失败:", err) os.Exit(1) } }