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引用而非直接对象引用来构建树形结构。

回到顶部