什么输入会导致 Golang 的 json. Marshall 返回错误?

来自 医生:

JSON 不能表示循环数据结构,Marshall 不处理它们。将循环结构传递给 Marshall 将导致无限递归。

我曾经经历过这种情况,它会导致运行时恐慌。

我想知道的是,是否有人能提供一个工作程序,演示一个非恐慌的情况下,其中 json。元帅返回一个非零错误。最好的答案是 清楚包括所使用的输入

43352 次浏览

更新: 现在使用通道而不是 map [ int ] int 来引发错误


特定的结构,例如 funcchan拒绝序列化:

package main


import (
"encoding/json"
"fmt"
)


func main() {
value := make(chan int)
_, err := json.Marshal(value)
fmt.Println(err)
}

为了补充 Jonathan 的答案,json.Marshall 函数可以返回两种类型的错误: UnsupportedTypeErrorUnsupportedValueError

正如乔纳森所说的那样,第一种可能是由于试图将一种无效的类型封装起来而造成的:

_, err := json.Marshal(make(chan int))
_, ok := err.(*json.UnsupportedTypeError) // ok == true

另一方面,也可以让 Marshall 函数通过传递无效值返回错误:

_, err := json.Marshal(math.Inf(1))
_, ok := err.(*json.UnsupportedValueError) // ok == true

不久前,我解决了在 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。

恢复数据意味着一个反向操作,即任何引用都将被一个指向对象的指针替换。

附言。为什么是 tahwil?我试图为这个名字找一些不常见的单词,结果找到了一个阿拉伯语单词() ,意思是 转变

读取源代码可以找到这样一个函数来判断编码器是否存在将返回编组错误: 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))       // Complex128
json.Marshal(make(chan struct{})) // Chan
json.Marshal(func() {})           // Func