GO语言基础教程(227)Go文件处理之JSON文件的写入、读取操作:别卷了!Go语言文件处理之JSON魔法:把数据变成小纸条,还能变回来!

嘿,伙计们!今天咱们来聊一个每个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数据!

技术要点 & 避坑指南:

  1. json.Marshal:这是编码的核心函数。它接受一个Go对象,返回编码后的[]byte和一个error永远不要忘记处理这个error! 想象一下,如果你的结构体里有个不能序列化的类型(比如channel),这里就会报错。
  2. os.Create:它很“霸道”,如果文件已存在,会直接清空重写。如果想在文件末尾追加内容,需要用os.OpenFile with os.O_APPEND模式,那是后话了。
  3. 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
   装备:[屠龙刀 金丝甲 大还丹]

看!之前存进去的“小明”,又活生生地站在我们面前了!

技术要点 & 避坑指南:

  1. os.Open:以只读方式打开文件,不会清空原内容。
  2. io.ReadAll:简单粗暴,把整个文件内容读到一个[]byte里。对于大文件,这会很吃内存,更优雅的方式是使用流式解码json.NewDecoder(file).Decode(&loadedPlayer),但今天我们学基础的,先掌握这个。
  3. 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.WriteFileos.ReadFile,以及让JSON更美观的json.MarshalIndent

总结

好了,Go语言处理JSON文件的“核心魔法”咱们今天就修炼到这里。我们来回顾一下咒语:

  1. 写入(序列化)data, err := json.Marshal(你的结构体) -> os.WriteFile(文件名, data, 权限)
  2. 读取(反序列化)data, err := os.ReadFile(文件名) -> err = json.Unmarshal(data, &你的结构体指针)
  3. 美容(标签):在结构体字段后加 `json:"字段名,选项"`,控制编码行为。
  4. 好习惯:永远处理错误(err),永远defer file.Close()

这套组合拳打下来,你的Go程序就彻底告别“金鱼记忆”,能够把自己的状态妥妥地保存下来,下次启动时又能满血复活。这不就是无数应用(游戏、工具、服务)的基石功能吗?

赶紧打开你的编辑器,把上面的代码敲一遍,你就能彻底掌握这个必杀技了。以后在Go的世界里,处理JSON文件就可以自信地说:“就这?”

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值