Golang json.Unmarshal
报错 invalid character '\x01' in string literal
,意味着你尝试解析的 JSON 字符串中包含了 ASCII 控制字符 \x01
(Start of Heading),而标准的 JSON 规范是不允许在字符串字面量中出现这类未经转义的控制字符的。
问题原因
你接收到的数据可能不是一个纯净的 JSON 字符串,其中混入了一些非打印字符或者二进制数据。这通常发生在以下几种情况:
- 数据源问题: 数据在生成或传输过程中被污染,例如来自某些数据库字段、网络协议、或者其他系统,它们在 JSON 数据之前或之后附加了额外的非 JSON 内容。
- 编码问题: 数据的编码不是 UTF-8,或者在传输过程中发生了编码错误,导致某些字节被错误地解释为控制字符。
- 日志或调试输出: 有时候在调试过程中,一些二进制数据或者控制字符会意外地混入到 JSON 字符串中。
- 文件读取问题: 如果是从文件中读取 JSON,文件可能包含了 BOM (Byte Order Mark) 或者其他非文本内容。
如何解决
解决这个问题的主要思路是在 json.Unmarshal
之前,对输入数据进行清洗,移除或替换掉这些非法字符。
以下是一些常见的解决方案:
1. 过滤非法字符(推荐)
这是最常见和推荐的方法。在解析 JSON 之前,遍历字符串,移除或替换掉非法的控制字符。
package main
import (
"encoding/json"
"fmt"
"strings"
"unicode"
)
func main() {
// 假设这是你收到的包含非法字符的 JSON 字符串
// 实际情况中,这个字符串可能来自网络、文件等
dirtyJSON := `{
"name": "John\x01Doe",
"age": 30,
"city": "New York\x02"
}`
// 模拟实际的错误情况,假设原始数据就是这样
// 如果直接这样写,Go 编译器会因为 \x01 是非法字符而报错
// 所以我们用 []byte 来模拟原始的字节流
// 实际中,你可能是从 io.Reader 或其他源读取到这样的 []byte
dirtyBytes := []byte(`{
"name": "John` + string([]byte{0x01}) + `Doe",
"age": 30,
"city": "New York` + string([]byte{0x02}) + `"
}`)
// 1. 清洗字符串
cleanedBytes := make([]byte, 0, len(dirtyBytes))
for _, b := range dirtyBytes {
// 检查是否是 ASCII 控制字符 (0x00-0x1F,不包括常用的制表符、换行符、回车符)
// 或者是非法的 ASCII 字符。
// 在 JSON 中,合法的控制字符只有 \t, \n, \r, \f, \b
// 其他控制字符都需要被转义或者是不允许的。
// 这里我们假设所有小于 0x20 (空格) 的非空白字符都是非法的,需要移除
if b >= 0x20 || b == '\t' || b == '\n' || b == '\r' {
cleanedBytes = append(cleanedBytes, b)
}
}
cleanJSON := string(cleanedBytes)
fmt.Println("Cleaned JSON:", cleanJSON)
var data map[string]interface{}
err := json.Unmarshal([]byte(cleanJSON), &data)
if err != nil {
fmt.Printf("Error unmarshaling cleaned JSON: %v\n", err)
} else {
fmt.Printf("Successfully unmarshaled cleaned JSON: %+v\n", data)
}
fmt.Println("\n--- 另一种更通用的过滤方法 ---")
// 2. 使用 unicode.IsPrint 或 unicode.IsControl 检查
// 这种方法更通用,但需要注意哪些控制字符在 JSON 中是合法的。
// 这里我们只保留可打印字符以及 JSON 合法的空白符。
cleanedStrBuilder := strings.Builder{}
for _, r := range string(dirtyBytes) {
if unicode.IsPrint(r) || unicode.IsSpace(r) { // IsSpace 包含了 \t, \n, \r
cleanedStrBuilder.WriteRune(r)
} else if r == '\b' || r == '\f' { // JSON 也允许 \b (backspace) 和 \f (form feed) 被转义
// 如果你期望保留这些并让它们在 JSON 中转义,这里需要特殊处理
// 但通常,直接移除这些非法控制字符是更安全的做法,因为它们可能不是预期的。
// 对于 \x01 这样的字符,它们既不是 IsPrint 也不是 IsSpace
// 所以它们会被过滤掉。
}
}
cleanJSON2 := cleanedStrBuilder.String()
fmt.Println("Cleaned JSON (unicode filter):", cleanJSON2)
var data2 map[string]interface{}
err2 := json.Unmarshal([]byte(cleanJSON2), &data2)
if err2 != nil {
fmt.Printf("Error unmarshaling cleaned JSON 2: %v\n", err2)
} else {
fmt.Printf("Successfully unmarshaled cleaned JSON 2: %+v\n", data2)
}
}
解释:
0x01
是一个 ASCII 控制字符,其十进制值为 1。在 JSON 字符串中,只有少数几个控制字符(\b
, \f
, \n
, \r
, \t
)是被允许的,并且需要用反斜杠 \
进行转义。其他所有的 ASCII 控制字符(0x00
到 0x1F
)都是不允许在未转义的情况下出现的。- 上面的代码通过遍历字节(或 rune),只保留那些被认为是合法的字符。
b >= 0x20
确保我们保留了所有的可打印 ASCII 字符(包括空格)。b == '\t' || b == '\n' || b == '\r'
则保留了 JSON 中合法的空白控制字符。 - 第二种方法使用
unicode.IsPrint
和 unicode.IsSpace
函数,这对于处理 Unicode 字符更为通用。它会保留所有可打印的 Unicode 字符和空白字符。
2. 检查数据来源和预处理
在编写代码解决之前,务必追溯问题的根源:
- API 接口: 如果数据来自某个 API,检查对方是否返回了标准 JSON。可以联系对方修正。
- 数据库: 如果数据来自数据库,检查存储的数据是否包含脏数据。可能需要在数据入库时就进行清洗。
- 文件: 如果是文件读取,检查文件编码以及是否有 BOM 头。
- 网络协议: 某些自定义协议可能在 JSON 数据前后附加额外信息,需要解析协议头/尾,只提取纯 JSON 部分。
3. 使用 bytes.ReplaceAll
进行替换(如果知道确切的非法字符)
如果非法字符是已知的,例如总是 \x01
,你可以使用 bytes.ReplaceAll
来将其替换为空字符串或合法的字符。
package main
import (
"encoding/json"
"bytes"
"fmt"
)
func main() {
dirtyBytes := []byte(`{
"name": "John` + string([]byte{0x01}) + `Doe",
"age": 30
}`)
// 将所有 \x01 替换为空
cleanedBytes := bytes.ReplaceAll(dirtyBytes, []byte{0x01}, []byte(""))
var data map[string]interface{}
err := json.Unmarshal(cleanedBytes, &data)
if err != nil {
fmt.Printf("Error unmarshaling: %v\n", err)
} else {
fmt.Printf("Successfully unmarshaled: %+v\n", data)
}
}
这种方法适用于你明确知道具体哪些非法字符需要处理的情况,但不如通用过滤方法灵活。
4. 自定义 Decoder(高级,通常不需要)
对于更复杂的场景,或者需要更细粒度的控制,你可以考虑实现 json.Decoder
的自定义行为,或者读取输入流并手动处理。但这通常是过度设计,对于 invalid character
错误,清洗输入流是更直接有效的方法。
总结和最佳实践:
- 优先处理数据源: 最佳解决方案是防止脏数据产生。检查你的数据源,确保它们只提供合法的 JSON。
- 前置清洗: 在调用
json.Unmarshal
之前,对输入的 []byte
或 string
进行清洗。遍历字符并移除所有非法的控制字符是最稳健的方法。 - 日志记录: 当发生这种错误时,记录下原始的非法数据(可以将其转换为十六进制或字符串表示),以便于调试和追溯问题源头。
选择最适合你场景的方法。通常情况下,通过遍历字符并过滤掉非打印/非法控制字符的方法(如方案 1)是最推荐且最健壮的。