什么输入会导致golang的json.Marshal返回错误?

时间:2015-11-24 20:51:18

标签: json go marshalling

来自docs

  

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

我遇到过这种情况,导致运行时恐慌。

我想知道的是,是否有人可以提供一个工作程序来演示json.Marshal返回非零错误的非恐慌情况。最好的答案明确包括使用的输入

4 个答案:

答案 0 :(得分:19)

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

第一个可以造成,正如乔纳森试图判定一个无效的类型:

_, 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特定结构,例如。 funcchan拒绝序列化:

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