嘿,伙计们!今天咱们来聊一个每个Go程序员都绕不开的、超级实用的话题——怎么跟JSON文件愉快地玩耍。
想象一下这个场景:你写了一个超牛的程序,运行起来嘎嘎快,数据在内存里活蹦乱跳。但程序一关闭,这些数据就“灰飞烟灭”了,下次启动又是一条“好汉”,从头开始。这怎么行?我们得让它学会“记笔记”,把重要的东西(比如用户配置、游戏存档、爬虫数据)写在小本本(也就是文件)上。
而这个“小本本”的最佳格式之一,就是JSON。它长得好看(结构化),人类能看懂,机器更能秒懂,是程序和程序之间、程序和人类之间沟通的“通用语言”。
在Go的世界里,处理JSON就像玩一个“变身”游戏。今天,咱就手把手教你,怎么用Go的魔法,把程序里的struct(结构体)变成JSON格式的字符串,然后塞进文件里(这叫序列化或编码);反过来,再把文件里的JSON字符串读出来,还原成程序里的struct(这叫反序列化或解码)。
第一幕:召唤神龙——引入核心包
任何魔法都需要咒语,在Go里,咒语就是各种各样的包。处理JSON,我们主要请出这两位“大神”:
import (
"encoding/json" // 核心魔法,负责变身
"os" // 文件操作苦力,负责读写小纸条
)
encoding/json:这是我们的魔法核心,所有的“变身”法术都在这里。
os:这是我们的文件小助手,负责创建、打开、关闭文件这些脏活累活。
第二幕:准备“变身”素材——定义结构体
魔术师变兔子,得先有只兔子。我们要变JSON,也得先有数据。在Go里,最常用的就是结构体 (struct)。
假设我们要记录一个游戏玩家的信息:
type Player struct {
Name string
Level int
Gold float64
Equipped []string // 装备栏,一个字符串切片
}
瞧,我们定义了一个Player类型的结构体,它有名字、等级、金币和身上装备的列表。
第三幕:华丽变身之【写入JSON文件】
现在,我们有一个玩家对象,想把它变成JSON,存到文件player.json里。
完整代码如下,咱们边看边讲:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
type Player struct {
Name string
Level int
Gold float64
Equipped []string
}
func main() {
// 1. 创造一个玩家“小明”
player := Player{
Name: "小明",
Level: 99,
Gold: 9999.99,
Equipped: []string{"屠龙刀", "金丝甲", "大还丹"},
}
// 2. 将玩家结构体 编码(序列化)为 JSON 字节切片
// json.Marshal 是我们的第一个魔法,它能把Go的数据变成JSON格式的[]byte
data, err := json.Marshal(player)
if err != nil {
log.Fatal("变身(编码)失败:", err)
}
// 好奇宝宝可以看一眼变身后的样子
fmt.Println("变身后的JSON字符串:", string(data))
// 3. 创建文件,准备写入“小纸条”
// os.Create 会创建文件,如果已存在则清空。返回一个文件指针和可能的错误
file, err := os.Create("player.json")
if err != nil {
log.Fatal("创建小纸条(文件)失败:", err)
}
// 孟婆汤:defer 确保函数结束时文件一定会被关闭,防止内存泄露
defer file.Close()
// 4. 将JSON数据写入文件
// file.Write 就像用笔把内容写到纸条上
_, err = file.Write(data)
if err != nil {
log.Fatal("写小纸条失败:", err)
}
fmt.Println("✅ 玩家数据已成功写入 player.json!")
}
运行一下,你会看到控制台输出:
变身后的JSON字符串: {"Name":"小明","Level":99,"Gold":9999.99,"Equipped":["屠龙刀","金丝甲","大还丹"]}
✅ 玩家数据已成功写入 player.json!
同时,当前目录下会多出一个player.json文件,用记事本打开,里面就是整齐的JSON数据!
技术要点 & 避坑指南:
json.Marshal:这是编码的核心函数。它接受一个Go对象,返回编码后的[]byte和一个error。永远不要忘记处理这个error! 想象一下,如果你的结构体里有个不能序列化的类型(比如channel),这里就会报错。os.Create:它很“霸道”,如果文件已存在,会直接清空重写。如果想在文件末尾追加内容,需要用os.OpenFilewithos.O_APPEND模式,那是后话了。defer file.Close():这是一个超级好习惯! 确保文件被正确关闭,释放资源。想象你打开抽屉拿东西,拿完了要关上,不然会撞到人。程序也一样,打开文件不关,多了以后系统会受不了。
第四幕:完美还原之【读取JSON文件】
存进去了,还得能读出来。现在,我们假装程序重新启动了,需要从player.json里把玩家的数据读出来,重新创建一个Player结构体。
完整代码如下:
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
)
type Player struct {
Name string
Level int
Gold float64
Equipped []string
}
func main() {
// 1. 打开我们之前写的“小纸条”
file, err := os.Open("player.json") // os.Open 只读方式打开
if err != nil {
log.Fatal("找不到小纸条(文件)了:", err)
}
defer file.Close()
// 2. 读取文件的全部内容
// io.ReadAll 会一次性把文件所有内容读进内存,适合小文件。
data, err := io.ReadAll(file)
if err != nil {
log.Fatal("读小纸条失败:", err)
}
fmt.Println("从文件里读到的原始内容:", string(data))
// 3. 准备一个空的“容器”(Player结构体),用来承接数据
var loadedPlayer Player
// 4. 将JSON字节切片 解码(反序列化)到结构体中
// json.Unmarshal 是反向魔法,把JSON变回Go结构体
err = json.Unmarshal(data, &loadedPlayer) // 注意:这里要传指针 &!
if err != nil {
log.Fatal("还原(解码)失败:", err)
}
// 5. 炫耀一下我们还原出来的数据!
fmt.Printf("✅ 玩家数据还原成功!\n")
fmt.Printf(" 姓名:%s\n", loadedPlayer.Name)
fmt.Printf(" 等级:%d\n", loadedPlayer.Level)
fmt.Printf(" 金币:%.2f\n", loadedPlayer.Gold)
fmt.Printf(" 装备:%v\n", loadedPlayer.Equipped)
}
运行结果:
从文件里读到的原始内容: {"Name":"小明","Level":99,"Gold":9999.99,"Equipped":["屠龙刀","金丝甲","大还丹"]}
✅ 玩家数据还原成功!
姓名:小明
等级:99
金币:9999.99
装备:[屠龙刀 金丝甲 大还丹]
看!之前存进去的“小明”,又活生生地站在我们面前了!
技术要点 & 避坑指南:
os.Open:以只读方式打开文件,不会清空原内容。io.ReadAll:简单粗暴,把整个文件内容读到一个[]byte里。对于大文件,这会很吃内存,更优雅的方式是使用流式解码json.NewDecoder(file).Decode(&loadedPlayer),但今天我们学基础的,先掌握这个。json.Unmarshal:这是解码的核心函数。它接受JSON数据的[]byte和一个指向目标结构体的指针。
-
- 超级大坑:第二个参数必须是指针!如果你传了
loadedPlayer而不是&loadedPlayer,Go会在函数内部创建一个副本,解码操作只在副本上有效,外面的loadedPlayer依然是空的!记住,想修改一个变量,必须传它的地址。
- 超级大坑:第二个参数必须是指针!如果你传了
第五幕:魔法美容院——结构体标签
细心的你可能会发现,我们生成的JSON字段名,首字母都是大写的(Name, Level)。这在Go里是必须的,因为只有导出的字段(首字母大写)才能被json包访问到。
但有时候,我们想给JSON字段起个小写名,或者完全换个名字怎么办?这时候,结构体标签(Struct Tags) 就登场了!它就像给结构体字段贴了个“小纸条”,告诉json包:“请按我说的方式变身”。
type Player struct {
Name string `json:"name"` // 变身成JSON后,字段叫 "name"
Level int `json:"level"`
Gold float64 `json:"gold"`
Equipped []string `json:"equipped"`
}
用了标签后,生成的JSON就变成了:
{"name":"小明","level":99,"gold":9999.99,"equipped":["屠龙刀","金丝甲","大还丹"]}
看,字段名全都变成小写了!更符合JSON的通用习惯。
标签高级玩法:
json:"-":横杠表示这个字段在序列化和反序列化时直接被忽略。json:"equipped,omitempty":omitempty表示如果这个字段是零值(空切片、0、""、nil),序列化时就省略它,不出现在JSON中。
终幕:完整示例,一气呵成
最后,献上一个集大成的完整示例,同时演示写入和读取,并使用结构体标签:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
type Config struct {
AppName string `json:"app_name"`
Version string `json:"version"`
Debug bool `json:"debug,omitempty"` // 如果为false,就不出现在JSON里
Port int `json:"port"`
Database struct {
Host string `json:"host"`
User string `json:"user"`
} `json:"database"`
}
func main() {
// 创建一个配置对象
config := Config{
AppName: "我的超级应用",
Version: "v2.1.0",
Debug: false, // 这里是false,所以生成JSON时会被忽略
Port: 8080,
}
config.Database.Host = "localhost"
config.Database.User = "admin"
// 【写入】序列化并写入文件
dataToWrite, _ := json.MarshalIndent(config, "", " ") // 使用MarshalIndent生成带缩进的漂亮JSON
err := os.WriteFile("config.json", dataToWrite, 0644) // 使用更简洁的os.WriteFile一步到位
if err != nil {
log.Fatal("写入配置失败:", err)
}
fmt.Println("✅ 配置已写入 config.json")
// 【读取】从文件读取并反序列化
dataToRead, err := os.ReadFile("config.json") // 使用os.ReadFile一步到位读取文件
if err != nil {
log.Fatal("读取配置失败:", err)
}
var loadedConfig Config
err = json.Unmarshal(dataToRead, &loadedConfig)
if err != nil {
log.Fatal("解析配置失败:", err)
}
fmt.Printf("✅ 配置读取成功!\n")
fmt.Printf(" 应用名:%s, 版本:%s, 端口:%d\n", loadedConfig.AppName, loadedConfig.Version, loadedConfig.Port)
fmt.Printf(" 数据库:%s@%s\n", loadedConfig.Database.User, loadedConfig.Database.Host)
// 因为Debug是false且被omitempty,所以loadedConfig里Debug是零值false,我们也不显示它
}
这个例子用了更简洁的os.WriteFile和os.ReadFile,以及让JSON更美观的json.MarshalIndent。
总结
好了,Go语言处理JSON文件的“核心魔法”咱们今天就修炼到这里。我们来回顾一下咒语:
- 写入(序列化):
data, err := json.Marshal(你的结构体)->os.WriteFile(文件名, data, 权限)。 - 读取(反序列化):
data, err := os.ReadFile(文件名)->err = json.Unmarshal(data, &你的结构体指针)。 - 美容(标签):在结构体字段后加 `json:"字段名,选项"`,控制编码行为。
- 好习惯:永远处理错误(
err),永远defer file.Close()。
这套组合拳打下来,你的Go程序就彻底告别“金鱼记忆”,能够把自己的状态妥妥地保存下来,下次启动时又能满血复活。这不就是无数应用(游戏、工具、服务)的基石功能吗?
赶紧打开你的编辑器,把上面的代码敲一遍,你就能彻底掌握这个必杀技了。以后在Go的世界里,处理JSON文件就可以自信地说:“就这?”

被折叠的 条评论
为什么被折叠?



