Go JSON格式化输出到文件
在做配置文件导出功能时,直接用json.Marshal把数据转成JSON后写入文件,结果打开文件一看全是压缩成一行的乱码
实际可以用json.MarshalIndent做格式化缩进,还要指定编码避免乱码。
今天把总结的JSON格式化输出到文件的方法分享出来,希望对大家有所帮助。
先明确核心前提:
Go的encoding/json包是JSON处理的核心,Marshal生成压缩JSON,MarshalIndent生成带缩进的格式化JSON;
写入文件时需注意编码(默认UTF-8)和文件权限,否则易出现乱码或写入失败
一、简单数据格式化输出
如果要输出的是map、切片等基础数据类型,核心是用json.MarshalIndent生成带缩进的JSON字符串,再通过os.WriteFile写入文件。这种方式最简洁,适合配置文件、简单数据备份场景。
以导出系统配置(包含端口、超时时间、开关等)为例,实现格式化输出到文件:
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
// 1. 准备要输出的基础数据(Map类型,模拟系统配置)
config := map[string]interface{}{
"server": map[string]interface{}{
"port": 8080,
"timeout": 30,
"ssl": true,
"allowedIPs": []string{"127.0.0.1", "192.168.1.0/24"},
},
"log": map[string]interface{}{
"level": "info",
"path": "./logs/app.log",
},
}
// 2. JSON格式化处理:MarshalIndent(数据, 前缀, 缩进符)
// 前缀用"",缩进符用" "(两个空格,也可用"\t"制表符)
jsonData, err := json.MarshalIndent(config, "", " ")
if err != nil {
panic(fmt.Sprintf("JSON格式化失败:%v", err))
}
// 3. 写入文件:WriteFile(路径, 数据, 权限)
// 权限0644表示:所有者读写,其他只读(适合配置文件)
err = os.WriteFile("config.json", jsonData, 0644)
if err != nil {
panic(fmt.Sprintf("写入文件失败:%v", err))
}
fmt.Println("JSON格式化输出到文件成功!")
}- 格式化核心:
json.MarshalIndent(config, "", " ")的三个参数分别是“待处理数据”“每行前缀”“缩进字符”,用两个空格缩进生成的JSON结构清晰,符合通用阅读习惯。 - 文件权限:0644是配置文件常用权限,避免因权限过高导致安全风险,也防止权限不足无法读取;如果是可执行脚本,可改用0755。
- 编码问题:
json.MarshalIndent生成的是UTF-8编码字节流,os.WriteFile默认按UTF-8写入,Windows下打开也不会乱码(避免用记事本直接打开,建议用VS Code)。
二、结构体数据格式化输出
业务开发中更常见的是将结构体数据(如用户信息、订单数据)转成JSON输出到文件,这就需要注意结构体字段导出和Tag配置——字段未导出会导致JSON为空,Tag用于指定JSON键名和忽略空值等。
以导出用户列表为例,结构体字段加Tag控制JSON键名、忽略空值,实现格式化输出:
package main
import (
"encoding/json"
"fmt"
"os"
"time"
)
// User 业务结构体(模拟用户数据)
// Tag含义:json:"键名,omitempty" 键名指定,空值忽略
type User struct {
Username string `json:"username"` // 必选字段,指定键名
Age int `json:"age,omitempty"` // 可选字段,空值不输出
Email string `json:"email"` // 必选字段
RegisterAt time.Time `json:"register_at"` // 时间字段自动转ISO格式
IsVip bool `json:"is_vip"` // 布尔字段
}
func main() {
// 1. 准备结构体数据(用户列表)
users := []User{
{
Username: "zhangsan",
Age: 25,
Email: "zhangsan@example.com",
RegisterAt: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
IsVip: true,
},
{
Username: "lisi",
// Age留空,Tag指定omitempty,JSON中不输出该字段
Email: "lisi@example.com",
RegisterAt: time.Date(2024, 2, 20, 14, 10, 0, 0, time.UTC),
IsVip: false,
},
}
// 2. JSON格式化:缩进用"\t"制表符,结构更整齐
jsonData, err := json.MarshalIndent(users, "", "\t")
if err != nil {
panic(fmt.Sprintf("结构体JSON格式化失败:%v", err))
}
// 3. 写入文件:覆盖写入(若文件存在则替换)
err = os.WriteFile("user_list.json", jsonData, 0644)
if err != nil {
panic(fmt.Sprintf("写入用户列表失败:%v", err))
}
fmt.Println("结构体JSON格式化输出成功!")
}- 字段必须导出:结构体字段首字母必须大写(如
Username),否则json包无法访问,输出的JSON会是空对象{}。 - Tag配置技巧:常用Tag参数有
omitempty(空值忽略)、"-"(不输出该字段)、"key,omitempty"(指定键名+空值忽略),比如Age intjson:“user_age,omitempty”``。` - 时间字段处理:
time.Time类型会自动转成ISO 8601格式(如"2024-01-15T10:30:00Z"),若需自定义格式,需实现json.Marshaler接口。
当然,如果想让时间字段输出为"2024-01-15 10:30:00"格式,自定义时间类型并实现MarshalJSON方法:
// 自定义时间类型
type CustomTime time.Time
// 实现json.Marshaler接口的MarshalJSON方法
func (ct CustomTime) MarshalJSON() ([]byte, error) {
// 自定义时间格式
formatted := time.Time(ct).Format("2006-01-02 15:04:05")
// 包裹成JSON字符串(需加引号)
return []byte(fmt.Sprintf("\"%s\"", formatted)), nil
}
// 结构体中使用自定义时间类型
type User struct {
Username string `json:"username"`
RegisterAt CustomTime `json:"register_at"`
}
// 使用时转换time.Time为CustomTime
users := []User{
{
Username: "zhangsan",
RegisterAt: CustomTime(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)),
},
}进阶:批量/大JSON数据输出
如果要输出十万条以上的批量数据(如日志导出、数据备份),直接用MarshalIndent会把所有数据加载到内存,导致内存溢出。此时需用“流式写入”——逐行写入JSON数组,降低内存占用。
流式写入实现代码(批量用户数据)
package main
import (
"encoding/json"
"fmt"
"os"
"time"
)
type User struct {
Username string `json:"username"`
Age int `json:"age"`
RegisterAt time.Time `json:"register_at"`
}
func main() {
// 1. 创建文件并打开(读写模式,不存在则创建,存在则清空)
file, err := os.OpenFile("batch_users.json", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
panic(fmt.Sprintf("创建文件失败:%v", err))
}
// 关键:延迟关闭文件,避免资源泄漏
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("关闭文件失败:%v", err)
}
}()
// 2. 写入JSON数组开头
_, err = file.WriteString("[\n")
if err != nil {
panic(fmt.Sprintf("写入数组开头失败:%v", err))
}
// 3. 模拟批量数据(10万条,实际场景从数据库查询)
batchSize := 100000
for i := 0; i < batchSize; i++ {
user := User{
Username: fmt.Sprintf("user_%d", i),
Age: 18 + i%20,
RegisterAt: time.Now().AddDate(0, -i%12, 0),
}
// 格式化单条数据,前缀加制表符,缩进用两个空格
jsonData, err := json.MarshalIndent(user, "\t", " ")
if err != nil {
panic(fmt.Sprintf("格式化第%d条数据失败:%v", i, err))
}
// 写入单条数据
_, err = file.Write(jsonData)
if err != nil {
panic(fmt.Sprintf("写入第%d条数据失败:%v", i, err))
}
// 除最后一条外,末尾加逗号和换行
if i != batchSize-1 {
_, err = file.WriteString(",\n")
} else {
_, err = file.WriteString("\n")
}
if err != nil {
panic(fmt.Sprintf("写入分隔符失败:%v", err))
}
}
// 4. 写入JSON数组结尾
_, err = file.WriteString("]")
if err != nil {
panic(fmt.Sprintf("写入数组结尾失败:%v", err))
}
fmt.Printf("成功输出%d条数据到文件!", batchSize)
}核心优势:每次只处理一条数据并写入,内存占用始终保持在低水平,即使处理百万条数据也不会内存溢出。
常见问题
Q1. 问题:JSON文件打开后中文乱码?
最常见的坑!原因是Windows系统默认用GBK编码打开文件,而Go默认按UTF-8写入,编码不匹配导致乱码。
解决:
① 写入时指定UTF-8 BOM头(让Windows记事本识别UTF-8);
② 用VS Code、Notepad++等编辑器打开(自动识别UTF-8)。
示例代码:
// 写入UTF-8 BOM头(解决Windows记事本乱码)
_, err = file.WriteString("\xEF\xBB\xBF")
if err != nil {
panic(err)
}
// 再写入JSON数据
_, err = file.Write(jsonData)Q2. 问题:结构体转JSON后字段缺失或为空?
90%是因为结构体字段未导出(首字母小写),json包无法访问该字段;另外Tag配置错误(如键名拼写错误)也会导致字段缺失。
解决:
① 所有需要输出的字段首字母必须大写;
② 检查Tag配置,确保键名正确,避免多写或少写字符(如json:"user_name"不要写成json:"username")。
Q3. 问题:格式化后的JSON有多余空格或换行?
原因是MarshalIndent的前缀或缩进符配置不当,或写入时手动添加了多余的分隔符。
解决:
① 前缀用""或"\t",缩进符统一用" "(两个空格)或"\t",不要混合使用;
② 批量写入时严格控制逗号和换行,最后一条数据不加逗号。
Q4. 问题:大JSON数据写入时内存溢出?
原因是直接用MarshalIndent处理整个批量数据,把所有数据加载到内存导致溢出。
解决:
① 采用“流式写入”方案,逐行处理单条数据并写入文件,如前面进阶场景的示例代码;
② 如果是JSON数组,手动拼接数组开头、单条数据、分隔符和数组结尾。
Q5. 问题:写入文件时提示“permission denied”?
原因是文件路径不存在(如写入./logs/data.json但logs目录未创建),或当前用户没有写入权限。
解决:
① 写入前检查目录是否存在,不存在则创建;
② 配置正确的文件权限(如0644、0755)。
示例代码:
import "os"
// 检查并创建目录(递归创建多级目录)
err = os.MkdirAll("./logs", 0755)
if err != nil {
panic(fmt.Sprintf("创建目录失败:%v", err))
}
// 再写入文件(路径存在,权限正确)
err = os.WriteFile("./logs/data.json", jsonData, 0644)总结
- 简单数据场景:直接用
json.MarshalIndent + os.WriteFile,代码最简洁,适合配置文件、小数据备份; - 结构体场景:定义结构体并配置Tag,用
MarshalIndent格式化,控制字段导出和键名,适合业务数据导出; - 批量/大数据场景:用
os.OpenFile创建文件,逐行流式写入JSON数组,避免内存溢出,适合日志、海量数据备份。
最后注意: 结构体字段首字母大写,中文乱码加UTF-8 BOM头,大数据用流式写入,写入前检查目录权限。
如果大家在处理特殊JSON格式(如嵌套JSON、JSON Lines)时遇到问题,欢迎在评论区交流~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!