Golang中如何定位JSON解析失败的具体行数?

Golang中如何定位JSON解析失败的具体行数? 我正在开发一个使用 Go 解析 JSON 配置文件的应用。我遇到了如下所示的错误,但它没有告诉我 JSON 文件中是哪一行导致了这个问题。我该如何查看这个信息?

panic: json: cannot unmarshal string into Go value of type map[string]*json.RawMessage
12 回复

打印日志以显示 JSON 字符串

更多关于Golang中如何定位JSON解析失败的具体行数?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我不知道在 Go 中有任何方法可以做到这一点。

我无法访问代码。我原本希望有一种方法能像在Rust中那样开启更详细的日志。

json.Unmarshal 会返回一个 *json.SyntaxError,该错误包含一个 Offset 字段,该字段记录了在语法错误发生前已成功读取的字节数。

在使用JSON反序列化库或函数时,请检查反序列化过程中提供的错误信息。错误信息可能包含导致失败的具体行、字段或元素的信息。这可以为你定位JSON数据中的问题提供一个起点。

要识别导致错误的JSON文件中的行,您可以使用JSON验证器或格式化工具。这些工具可以通过高亮显示有问题的行或显示描述问题的错误信息,来帮助您识别JSON文件中的语法错误。

您可以检查您的Go代码,看看哪里有一个类型为 map[string]*json.RawMessage 的字段,然后检查您的JSON文件,看看它是否是一个字符串。

func main() {
    fmt.Println("hello world")
}

如果JSON无效,这会有帮助。但是,如果你在期望对象的地方放了一个JSON字符串值(例如 {"key": "string"} 对比 {"key": {"subkey": "string"}}),我认为除非你有一个JSON模式定义,否则这不会有帮助。

啊,说得好——我错误地认为它是无效的JSON,因为 map[string]*json.RawMessage 非常灵活(它能很好地处理你提到的两个例子)。但你完全正确——仅以数组作为顶层文本是有效的JSON:

stackoverflow.com

在这种情况下,map[string] 会失效。

原来这个错误是一个 *json.UnmarshalTypeError,该类型有一个 Offset 字段,它会告诉你错误发生时解析器所在的位置。它应该指向未能成功解组(unmarshal)的值的末尾:The Go Play Space

func main() {
    fmt.Println("hello world")
}

你能把你的JSON粘贴到一个在线JSON解析器中,看看是哪一行出了问题吗?你也可以使用Go Playground安排一个快速测试,并尝试通过这种方式进行调试。例如,修改这个Playground链接以包含你实际的JSON:

func main() {
	// 用你的JSON替换这里的内容
	byt := []byte(`{ "value1": "test", "value2": }`)
	var dat map[string]*json.RawMessage
	if err := json.Unmarshal(byt, &dat); err != nil {
		panic(err)
	}
	fmt.Println(dat)
}

该代码的输出至少比你的可执行文件给出的信息更具描述性:

panic: invalid character '}' looking for beginning of value

goroutine 1 [running]:
main.main()
	/tmp/sandbox266866198/prog.go:13 +0xe7

Program exited.

你也可以尝试结合使用这里概述的流程和那个Go Playground链接来获取更多信息:

获取Go的JSON反序列化错误中的行号和字符位置

软件工程博客

不过,再次说明,一个在线JSON解析器应该能很好地为你提供详细的错误信息。

在Go标准库的encoding/json包中,解析错误默认不提供行号信息。你可以通过自定义错误处理来定位问题行数。以下是两种实用方法:

方法1:使用json.Decoder并记录行号

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "strings"
)

type lineTrackingReader struct {
    r io.Reader
    line int
    column int
    lastByte byte
}

func (l *lineTrackingReader) Read(p []byte) (n int, err error) {
    n, err = l.r.Read(p)
    for i := 0; i < n; i++ {
        if l.lastByte == '\n' {
            l.line++
            l.column = 0
        }
        l.column++
        l.lastByte = p[i]
    }
    return n, err
}

func (l *lineTrackingReader) Position() (line, column int) {
    return l.line + 1, l.column
}

func main() {
    jsonStr := `{
    "name": "test",
    "data": "invalid"  // 这里应该是对象,但给了字符串
}`

    reader := &lineTrackingReader{
        r: strings.NewReader(jsonStr),
        line: 0,
        column: 0,
    }
    
    var result map[string]interface{}
    decoder := json.NewDecoder(reader)
    err := decoder.Decode(&result)
    
    if err != nil {
        line, col := reader.Position()
        fmt.Printf("JSON解析错误在第 %d 行,第 %d 列附近: %v\n", line, col, err)
    }
}

方法2:使用json.RawMessage和分步解析

package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

func locateJSONError(jsonStr string) {
    lines := strings.Split(jsonStr, "\n")
    
    // 尝试解析整个JSON
    var raw map[string]json.RawMessage
    if err := json.Unmarshal([]byte(jsonStr), &raw); err != nil {
        // 获取错误中的字节位置
        if syntaxErr, ok := err.(*json.SyntaxError); ok {
            offset := syntaxErr.Offset
            
            // 计算行号
            line := 1
            charCount := 0
            for i, lineStr := range lines {
                charCount += len(lineStr) + 1 // +1 for newline
                if charCount >= int(offset) {
                    line = i + 1
                    // 计算列号
                    prevLinesLength := 0
                    for j := 0; j < i; j++ {
                        prevLinesLength += len(lines[j]) + 1
                    }
                    column := int(offset) - prevLinesLength
                    
                    fmt.Printf("语法错误在第 %d 行,第 %d 列附近\n", line, column)
                    fmt.Printf("错误行内容: %s\n", lines[i])
                    fmt.Printf("错误详情: %v\n", err)
                    return
                }
            }
        }
        fmt.Printf("解析错误: %v\n", err)
    }
}

func main() {
    jsonStr := `{
    "name": "test",
    "config": {
        "port": 8080,
        "host": "localhost"
    },
    "data": "should be object"  // 错误行
}`

    locateJSONError(jsonStr)
}

方法3:使用第三方库

安装支持行号定位的JSON库:

go get github.com/tidwall/gjson
package main

import (
    "fmt"
    "github.com/tidwall/gjson"
)

func main() {
    jsonStr := `{
    "name": "test",
    "data": "invalid string instead of object"
}`

    // 验证JSON语法
    if !gjson.Valid(jsonStr) {
        // gjson会返回更详细的错误信息
        result := gjson.Parse(jsonStr)
        fmt.Printf("JSON解析失败: %v\n", result)
    }
    
    // 或者使用Get方法时检查错误
    result := gjson.Get(jsonStr, "data")
    if !result.Exists() {
        fmt.Println("字段不存在或解析失败")
    }
}

这些方法可以帮助你定位JSON解析失败的具体位置。第一种方法通过自定义Reader跟踪行号,第二种方法通过计算字节偏移量转换为行号,第三种方法使用第三方库获得更好的错误信息。

回到顶部