Golang中JSON Marshal在意外循环情况下遇到的致命错误
Golang中JSON Marshal在意外循环情况下遇到的致命错误
我正在开发一个应用程序,其中有一个结构体包含了其父级和子级的引用信息。
我一直没明白我的应用程序出了什么问题,直到我开始准备在这里提交错误报告时忘记加载子块的父级数据。加载后程序就崩溃了。
这个问题能否解决是另一回事,但目前我建议json包在这种情况下能够返回错误信息(如果可能的话)。
https://play.golang.org/p/sJ2rki6zm7J
func main() {
fmt.Println("hello world")
}
2 回复
检测这一点很棘手。JSON 编组器有文档说明不会这样做。
更多关于Golang中JSON Marshal在意外循环情况下遇到的致命错误的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go语言中,当使用json.Marshal对包含循环引用的结构体进行序列化时,确实会遇到致命错误。这是因为JSON编码器无法处理无限递归的结构,最终会导致栈溢出。
下面是一个重现问题和解决方案的示例:
问题重现
package main
import (
"encoding/json"
"fmt"
)
type Node struct {
Name string
Parent *Node
Children []*Node
}
func main() {
// 创建循环引用
parent := &Node{Name: "Parent"}
child := &Node{Name: "Child", Parent: parent}
parent.Children = []*Node{child}
// 尝试序列化会导致栈溢出
_, err := json.Marshal(parent)
if err != nil {
fmt.Printf("序列化错误: %v\n", err)
}
}
解决方案
方法1:使用自定义MarshalJSON方法
package main
import (
"encoding/json"
"fmt"
)
type Node struct {
Name string
Parent *Node `json:"-"`
Children []*Node
}
func (n *Node) MarshalJSON() ([]byte, error) {
type Alias Node
return json.Marshal(&struct {
*Alias
ParentName string `json:"parentName,omitempty"`
}{
Alias: (*Alias)(n),
ParentName: func() string {
if n.Parent != nil {
return n.Parent.Name
}
return ""
}(),
})
}
func main() {
parent := &Node{Name: "Parent"}
child := &Node{Name: "Child", Parent: parent}
parent.Children = []*Node{child}
data, err := json.Marshal(parent)
if err != nil {
fmt.Printf("序列化错误: %v\n", err)
return
}
fmt.Printf("序列化结果: %s\n", string(data))
}
方法2:使用omitempty和指针检查
package main
import (
"encoding/json"
"fmt"
)
type SafeNode struct {
Name string
Parent *SafeNode `json:"parent,omitempty"`
Children []*SafeNode
}
func main() {
parent := &SafeNode{Name: "Parent"}
child := &SafeNode{Name: "Child"}
// 避免直接循环引用
parent.Children = []*SafeNode{child}
// 不设置child.Parent = parent来避免循环
data, err := json.Marshal(parent)
if err != nil {
fmt.Printf("序列化错误: %v\n", err)
return
}
fmt.Printf("序列化结果: %s\n", string(data))
}
方法3:使用映射而非直接引用
package main
import (
"encoding/json"
"fmt"
)
type NodeWithID struct {
ID string
Name string
ParentID string `json:"parentId,omitempty"`
Children []string `json:"children,omitempty"`
}
func main() {
nodes := map[string]*NodeWithID{
"parent": {ID: "parent", Name: "Parent", Children: []string{"child"}},
"child": {ID: "child", Name: "Child", ParentID: "parent"},
}
data, err := json.Marshal(nodes)
if err != nil {
fmt.Printf("序列化错误: %v\n", err)
return
}
fmt.Printf("序列化结果: %s\n", string(data))
}
这些方法通过避免直接循环引用或使用自定义序列化逻辑来防止JSON编码器陷入无限递归。在实际应用中,建议使用ID引用而非直接对象引用来构建树形结构。

