Go 格式化修复 JSON 字符串
做开发的同学肯定都遇到过这种糟心情况:对接第三方接口时,对方返回的 JSON 键没加双引号;或者手动写 JSON 配置时,不小心多打了个尾逗号,导致解析直接报错。
今天就用 Go 手把手教大家解决这个问题,不管是简单的语法纠错还是复杂的格式修复,都能搞定。
当然最好还是和第三方接口进行沟通,确保是正确的 JSON 格式,避免很多麻烦
一、JSON 为什么会解析失败?
JSON 有严格的语法规范,很多看似“差不多”的写法其实都是无效的,常见的错误类型有这几种:
- 键未用双引号包裹:比如
{name:"张三"},正确写法是{"name":"张三"} - 字符串用单引号:比如
{"name":'张三'},JSON 只支持双引号 - 末尾多逗号:比如
{"name":"张三","age":18,},尾部逗号不允许 - 注释干扰:JSON本身不支持注释,但有人会加
//或/* */注释 - 特殊字符未转义:比如字符串里包含双引号
{"desc":"他说"你好""},未转义导致解析中断
Go 的标准库encoding/json对语法要求很严格,遇到以上情况会直接返回解析错误。
二、基础修复
用原生库+自定义逻辑解决常见问题
对于键无引号、单引号转双引号这类简单错误,不需要引入第三方库,用 Go 的字符串处理和正则就能解决。
先实现一个基础版修复工具,覆盖 80% 的常见场景
基础修复主要针对 3 类问题:键加双引号、单引号转双引号、去除尾逗号。
核心思路是:先用正则处理语法错误,再用标准库解析验证,确保修复后的数据有效
代码示例如下:
package main
import (
"encoding/json"
"fmt"
"regexp"
"strings"
)
// FixBasicJSON 修复基础不规范JSON:键加双引号、单引号转双引号、去除尾逗号
// 参数:rawJSON 原始不规范JSON字符串
// 返回:修复后的规范JSON、错误信息
func FixBasicJSON(rawJSON string) (string, error) {
// 1. 去除JSON中的注释(// 单行注释和 /* */ 多行注释)
// 匹配单行注释
commentRegex1 := regexp.MustCompile(`//.*\n`)
rawJSON = commentRegex1.ReplaceAllString(rawJSON, "\n")
// 匹配多行注释
commentRegex2 := regexp.MustCompile(`/\*[\s\S]*?\*/`)
rawJSON = commentRegex2.ReplaceAllString(rawJSON, "")
// 2. 将单引号替换为双引号(注意避免替换字符串内部的单引号,这里做基础处理,复杂场景需调整)
rawJSON = strings.ReplaceAll(rawJSON, "'", "\"")
// 3. 为未加双引号的键添加双引号(匹配类似 key: 的格式,排除已加引号的情况)
keyRegex := regexp.MustCompile(`([{,]\s*)([a-zA-Z0-9_]+)(\s*:)`)
rawJSON = keyRegex.ReplaceAllString(rawJSON, "$1\"$2\"$3")
// 4. 去除对象/数组末尾的逗号(匹配 }, 或 ], 格式)
trailingCommaRegex := regexp.MustCompile(`,\s*([}\]])`)
rawJSON = trailingCommaRegex.ReplaceAllString(rawJSON, " $1")
// 5. 验证修复后的JSON是否合法
var temp interface{}
if err := json.Unmarshal([]byte(rawJSON), &temp); err != nil {
return "", fmt.Errorf("修复后JSON仍不合法:%w,修复后内容:%s", err, rawJSON)
}
// 6. 格式化JSON(可选,按需求决定是否格式化)
formattedJSON, err := json.MarshalIndent(temp, "", " ")
if err != nil {
return "", fmt.Errorf("JSON格式化失败:%w", err)
}
return string(formattedJSON), nil
}
func main() {
// 测试:不规范的JSON示例
rawJSON := `{
name: '张三', // 姓名
age: 18,
address: {
city: '北京',
street: "长安街"
},
hobbies: ['读书', '运动'],
}`
// 修复JSON
fixedJSON, err := FixBasicJSON(rawJSON)
if err != nil {
fmt.Printf("JSON修复失败:%v\n", err)
return
}
fmt.Println("修复后的规范JSON:")
fmt.Println(fixedJSON)
}- 适用场景:键无引号、单引号、尾逗号、注释等基础语法错误,大部分对接第三方接口或手动写配置的场景都能覆盖。
- 使用方式:直接调用
FixBasicJSON函数,传入原始不规范 JSON,返回修复后的格式化 JSON。 - 验证机制:修复后会用
json.Unmarshal验证合法性,避免修复后仍无法解析的情况。
三、进阶修复
如果遇到更复杂的错误,比如嵌套层级极深、特殊字符未转义、语法错误较多的情况,原生正则处理容易顾此失彼。
这时推荐用成熟的第三方库github.com/tidwall/gjson和github.com/tidwall/sjson,它们对不规范 JSON 的兼容性更好,还能精准定位错误位置。
这些库的优势如下:
- 支持键无引号、单引号等不规范语法的解析
- 能精准定位 JSON 解析错误的行号和列号
- 提供灵活的 JSON 读写 API,修复后可直接修改数据
代码示例:
package main
import (
"encoding/json"
"fmt"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// FixComplexJSON 复杂JSON修复:基于第三方库,支持更复杂的语法错误
// 参数:rawJSON 原始不规范JSON字符串
// 返回:修复后的规范JSON、错误信息
func FixComplexJSON(rawJSON string) (string, error) {
// 1. 用gjson解析不规范JSON(gjson对不规范语法兼容性强)
result := gjson.Parse(rawJSON)
// 检查解析是否有错误
if result.Error != nil {
return "", fmt.Errorf("JSON解析失败:%w(错误位置:行%d,列%d)",
result.Error, result.Line, result.Column)
}
// 2. 将解析结果转为Go原生数据类型(确保数据结构正确)
var data interface{}
if err := json.Unmarshal([]byte(result.Raw), &data); err != nil {
return "", fmt.Errorf("转换数据结构失败:%w", err)
}
// 3. 格式化JSON(可根据需求调整缩进)
formattedJSON, err := json.MarshalIndent(data, "", " ")
if err != nil {
return "", fmt.Errorf("格式化JSON失败:%w", err)
}
// 4. 可选:修改JSON中的特定字段(示例:将age字段加1)
modifiedJSON, err := sjson.Set(string(formattedJSON), "age", 19)
if err != nil {
return "", fmt.Errorf("修改JSON字段失败:%w", err)
}
return modifiedJSON, nil
}
func main() {
// 测试:更复杂的不规范JSON
rawJSON := `{
name: '张三',
age: 18,
address: {
city: '北京',
street: "长安街",
zip: 100000, // 邮编
},
hobbies: ['读书', "运动", '编程'],
info: {
"height": 180,
weight: 70,
}
}`
// 进阶修复
fixedJSON, err := FixComplexJSON(rawJSON)
if err != nil {
fmt.Printf("复杂JSON修复失败:%v\n", err)
return
}
fmt.Println("进阶修复后的JSON:")
fmt.Println(fixedJSON)
}gjson能解析大部分不规范JSON,还能返回错误位置,方便定位问题;sjson支持精准修改JSON字段,修复后可直接调整数据。
常见问题
Q1. 修复后 JSON 仍解析失败?
原因:可能存在特殊字符未转义(比如字符串中的双引号、换行符),或语法错误过于严重(比如括号不匹配)。
解决:用gjson.Parse获取错误位置(行号和列号),手动检查原始 JSON 对应位置的语法;对特殊字符,可添加转义处理逻辑:
// 转义字符串中的双引号和换行符
func escapeSpecialChars(s string) string {
s = strings.ReplaceAll(s, "\"", "\\\"")
s = strings.ReplaceAll(s, "\n", "\\n")
return s
}Q2. 处理超大 JSON 时内存溢出?
原因:直接将超大 JSON 加载到内存解析,导致内存占用过高。
解决:采用流式解析,使用encoding/json的Decoder逐行解析,避免一次性加载整个 JSON:
import (
"encoding/json"
"os"
)
// 流式解析超大JSON
func StreamParseJSON(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
decoder := json.NewDecoder(file)
// 解析JSON开头的 { 或 [
token, err := decoder.Token()
if err != nil {
return err
}
fmt.Printf("JSON类型:%v\n", token)
// 逐字段解析
for decoder.More() {
var key, value interface{}
if err := decoder.Decode(&key); err != nil {
return err
}
if err := decoder.Decode(&value); err != nil {
return err
}
fmt.Printf("键:%v,值:%v\n", key, value)
}
return nil
}Q3. 第三方库解析结果和预期不一致?
原因:gjson对某些歧义语法的解析规则可能和预期不同,比如未定义的字段类型。
解决:在解析后通过json.Unmarshal转为原生数据类型,强制校验数据结构;或通过gjson的路径查询精准获取字段,确保解析结果正确:
// 用gjson路径查询精准获取字段
name := gjson.Get(rawJSON, "name").String()
age := gjson.Get(rawJSON, "address.zip").Int()
fmt.Printf("姓名:%s,邮编:%d\n", name, age)Q4. 如何批量修复多个 JSON 文件?
原因:需要处理目录下多个不规范的 JSON 配置文件,手动逐个修复效率低。
解决:结合目录遍历和修复函数,实现批量处理:
import (
"filepath"
"os"
)
// BatchFixJSON 批量修复目录下的JSON文件
func BatchFixJSON(dirPath string) error {
return filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 只处理JSON文件
if !info.IsDir() && filepath.Ext(path) == ".json" {
// 读取文件内容
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("读取文件[%s]失败:%w", path, err)
}
// 修复JSON
fixedContent, err := FixBasicJSON(string(content))
if err != nil {
return fmt.Errorf("修复文件[%s]失败:%w", path, err)
}
// 覆盖写入修复后的内容
if err := os.WriteFile(path, []byte(fixedContent), 0644); err != nil {
return fmt.Errorf("写入文件[%s]失败:%w", path, err)
}
fmt.Printf("成功修复文件:%s\n", path)
}
return nil
})
}总结
Go 处理不规范 JSON 时要“先容错解析,再规范格式化”。
简单场景用原生正则+标准库就能搞定,复杂场景推荐用gjson和sjson提升效率。
实际开发中,建议优先和对接方进行沟通,从源头解决问题,再根据 JSON 的复杂度选择合适的方案,同时做好修复后的验证,避免解析异常。
如果遇到更特殊的 JSON 错误场景,比如包含特殊编码或加密内容,欢迎在评论区交流,一起拆解解决!
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!