来自docs:
JSON不能表示循环数据结构,而Marshal不能处理它们。将循环结构传递给Marshal将导致无限递归。
我遇到过这种情况,导致运行时恐慌。
我想知道的是,是否有人可以提供一个工作程序来演示json.Marshal返回非零错误的非恐慌情况。最好的答案明确包括使用的输入。
答案 0 :(得分:19)
为了补充Jonathan的答案,json.Marshal函数可以返回两种类型的错误:UnsupportedTypeError
或UnsupportedValueError
第一个可以造成,正如乔纳森试图判定一个无效的类型:
_, err := json.Marshal(make(chan int))
_, ok := err.(*json.UnsupportedTypeError) // ok == true
另一方面,您也可以让Marshal函数通过传递无效值来返回错误:
_, err := json.Marshal(math.Inf(1))
_, ok := err.(*json.UnsupportedValueError) // ok == true
答案 1 :(得分:15)
更新:现在使用频道而不是map [int] int来引出错误
Go特定结构,例如。 func
或chan
拒绝序列化:
package main
import (
"encoding/json"
"fmt"
)
func main() {
value := make(chan int)
_, err := json.Marshal(value)
fmt.Println(err)
}
答案 2 :(得分:1)
阅读源代码后,您会发现该函数可以判断编码器是否不存在,将返回编组错误:https://github.com/golang/go/blob/master/src/encoding/json/encode.go
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
// ignored
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}
我们可以找到各种枚举 在https://github.com/golang/go/blob/master/src/reflect/type.go
因此,不难发现上述函数中没有的类型无法编组:
UnsafePointer,Complex64,Complex128,Chan,Func
示例:
json.Marshal(unsafe.Pointer(nil)) // UnsafePointer
json.Marshal(complex64(1)) // Complex64
json.Marshal(complex128(1)) // Complex64
json.Marshal(make(chan struct{})) // Chan
json.Marshal(func() {}) // Func
答案 3 :(得分:0)
前一段时间,我解决了在golang中对循环引用进行序列化/反序列化的问题,所有链接都转到了这个问题。但是,随着问题的扩大,这会引起误解。
如果遇到像我一样的情况,并且找不到如何处理循环引用的解决方案,则可以使用tahwil-我在github上发布的新库。据我所知,它现在是唯一以通用方式促进循环数据结构的序列化/反序列化的库。
Readme提供了有关如何使用该库的信息,因此我将在此处仅复制示例。
package main
import (
"encoding/json"
"fmt"
"github.com/go-extras/tahwil"
)
type Person struct {
Name string
Parent *Person
Children []*Person
}
func main() {
parent := &Person{
Name: "Arthur",
Children: []*Person{
{
Name: "Ford",
},
{
Name: "Trillian",
},
},
}
parent.Children[0].Parent = parent
parent.Children[1].Parent = parent
v, err := tahwil.ToValue(parent)
if err != nil {
panic(err)
}
res, err := json.Marshal(v)
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
package main
import (
"encoding/json"
"fmt"
"github.com/go-extras/tahwil"
)
type Person struct {
Name string `json:"name"`
Parent *Person `json:"parent"`
Children []*Person `json:"children"`
}
func prepareData() []byte {
parent := &Person{
Name: "Arthur",
Children: []*Person{
{
Name: "Ford",
},
{
Name: "Trillian",
},
},
}
parent.Children[0].Parent = parent
parent.Children[1].Parent = parent
v, err := tahwil.ToValue(parent)
if err != nil {
panic(err)
}
res, err := json.Marshal(v)
if err != nil {
panic(err)
}
return res
}
func main() {
data := &tahwil.Value{}
res := prepareData()
err := json.Unmarshal(res, data)
if err != nil {
panic(err)
}
person := &Person{}
err = tahwil.FromValue(data, person)
if err != nil {
panic(err)
}
fmt.Printf(`Name: %s
Children:
- %s
-- parent name: %s
- %s
-- parent name: %s
`, person.Name,
person.Children[0].Name,
person.Children[0].Parent.Name,
person.Children[1].Name,
person.Children[1].Parent.Name)
}
主要思想是将原始数据转换为tahwil.Value{}
,这实际上将refid
添加到您的所有字段中。每当tahwil
遇到循环引用时,它将用引用替换实际对象。之后,该图在技术上不再循环,因此可以将其编组为json。
还原数据意味着反向操作,即,任何引用将被指向对象的指针替换。
P.S。为什么tahwil
?我试图为该名称找到一个不常见的单词,然后找到一个阿拉伯语单词(تحويل),表示 conversion 。