如何解决 golang json.Unmarshal 报错:invalid character ‘’ in string literal

发布时间: 更新时间: 总字数:1991 阅读时间:4m 作者: IP上海 分享 网址

Golang json.Unmarshal 报错 invalid character '\x01' in string literal,意味着你尝试解析的 JSON 字符串中包含了 ASCII 控制字符 \x01 (Start of Heading),而标准的 JSON 规范是不允许在字符串字面量中出现这类未经转义的控制字符的。

问题原因

你接收到的数据可能不是一个纯净的 JSON 字符串,其中混入了一些非打印字符或者二进制数据。这通常发生在以下几种情况:

  1. 数据源问题: 数据在生成或传输过程中被污染,例如来自某些数据库字段、网络协议、或者其他系统,它们在 JSON 数据之前或之后附加了额外的非 JSON 内容。
  2. 编码问题: 数据的编码不是 UTF-8,或者在传输过程中发生了编码错误,导致某些字节被错误地解释为控制字符。
  3. 日志或调试输出: 有时候在调试过程中,一些二进制数据或者控制字符会意外地混入到 JSON 字符串中。
  4. 文件读取问题: 如果是从文件中读取 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 控制字符(0x000x1F)都是不允许在未转义的情况下出现的。
  • 上面的代码通过遍历字节(或 rune),只保留那些被认为是合法的字符。b >= 0x20 确保我们保留了所有的可打印 ASCII 字符(包括空格)。b == '\t' || b == '\n' || b == '\r' 则保留了 JSON 中合法的空白控制字符。
  • 第二种方法使用 unicode.IsPrintunicode.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 错误,清洗输入流是更直接有效的方法。

总结和最佳实践:

  1. 优先处理数据源: 最佳解决方案是防止脏数据产生。检查你的数据源,确保它们只提供合法的 JSON。
  2. 前置清洗: 在调用 json.Unmarshal 之前,对输入的 []bytestring 进行清洗。遍历字符并移除所有非法的控制字符是最稳健的方法。
  3. 日志记录: 当发生这种错误时,记录下原始的非法数据(可以将其转换为十六进制或字符串表示),以便于调试和追溯问题源头。

选择最适合你场景的方法。通常情况下,通过遍历字符并过滤掉非打印/非法控制字符的方法(如方案 1)是最推荐且最健壮的。

Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数