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

huangapple go评论88阅读模式
英文:

What input will cause golang's json.Marshal to return an error?

问题

文档中可以看到:

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

我遇到了这种情况,导致运行时发生 panic。

我想知道是否有人可以提供一个能够演示 json.Marshal 返回非空错误的工作程序,但不会导致 panic 的情况。最好的答案应该清楚地包含所使用的输入

英文:

From the docs:

> JSON cannot represent cyclic data structures and Marshal does not handle them. Passing cyclic structures to Marshal will result in an infinite recursion.

I've experienced this situation, which results in a runtime panic.

What I'm wondering is if anyone can provide a working program that demonstrates a non-panic situation where json.Marshal returns a non-nil error. The best answers would clearly include the inputs used.

答案1

得分: 63

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

第一种错误可能是由于尝试对无效类型进行编组而引起的,就像Jonathan所说的那样:

_, 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
英文:

Just to complement Jonathan's answer, the json.Marshal function can return two types of errors: UnsupportedTypeError or UnsupportedValueError

The first one can be caused, as Jonathan said by trying to Marshal an invalid type:

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

On the other hand you can also have the Marshal function return an error by passing an invalid value:

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

答案2

得分: 39

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


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

package main

import (
    "encoding/json"
    "fmt"
)

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

Update: now using a channel instead of a map[int]int to elicit the error


Go-specific structures,e.g. func or chan refuse to serialize:

package main

import (
    "encoding/json"
    "fmt"
)

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

答案3

得分: 16

阅读源代码,你可以找到一个函数来判断编码器是否存在,如果不存在将返回marshal错误:https://github.com/golang/go/blob/master/src/encoding/json/encode.go

func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
    // 忽略部分代码
	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
英文:

Read the source code you can found such a function to judge a encoder if not exist will return marshal error: 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
	}
}

We can find all kinds enum
at https://github.com/golang/go/blob/master/src/reflect/type.go

So it's not hard to see that kinds not in above function are unable to marshal:

UnsafePointer,Complex64,Complex128,Chan,Func

Examples:

        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

答案4

得分: 2

请注意,我是一个语言模型,我无法直接访问互联网上的链接。但是,你可以将链接中的代码复制粘贴到这里,我将帮助你进行翻译。

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Id      int64  `json:"id"`
	Name    string `json:"name"`
	Persons []Person
}

func main() {
	f := Person{Id: 1, Name: "shriprasad"}
	f.Persons = append(f.Persons, f)

	result, err := json.Marshal(f)
	fmt.Println("error " + err.Error())
	fmt.Println(string(result))
}

请注意,这是一个使用Go语言编写的简单程序。它定义了一个名为Person的结构体,该结构体具有Id、Name和Persons字段。在main函数中,创建了一个Person对象,并将其添加到Persons切片中。然后,使用json.Marshal函数将Person对象转换为JSON格式的字符串,并打印结果。

希望这可以帮助到你!如果你有任何其他问题,请随时问我。

英文:

https://go.dev/play/p/_Z29viT82CR.

Check this out -

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Id      int64  `json:"id"`
	Name    string `json:"name"`
	Persons []Person
}

func main() {
	f := Person{Id: 1, Name: "shriprasad"}
	f.Persons = append(f.Persons, f)

	result, err := json.Marshal(f)
	fmt.Println("error " + err.Error())
	fmt.Println(string(result))

}

答案5

得分: 1

一段时间前,我在解决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?我试图找一个不常见的词作为名称,并找到了一个阿拉伯词(تحويل),意思是“转换”。

英文:

A while ago I was solving a problem of serializing/deserializing cyclic references in golang, and all the links go to this question. However, it's slightly misleading as the question is broader.

If you got into the same situation like me, and can't find a solution on how to deal with cyclic references, you can now use tahwil - a new library that I published on github. To my knowledge it's now the only library that facilitates serialization/deserialization of cyclic data structures in a generic way.

Readme gives the information on how to use the library, so I will only duplicate the examples here.

Encoding:

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))
}

Decoding:

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)
}

The main idea is to transform the original data to tahwil.Value{}, which essentially adds refid's to all of your fields. Whenever tahwil encounters a cyclic reference, it replaces the actual object with a reference. And after that the graph is technically not cyclic anymore and thus can be marshalled to json.

Restoring the data means a reverse operation, i.e. any reference will be replaced by a pointer to an object.

P.S. Why tahwil? I tried to find some uncommon word for the name, and found an Arabic word (تحويل) that means conversion.

huangapple
  • 本文由 发表于 2015年11月25日 04:51:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/33903552.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定