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

后端 未结 4 1349
栀梦
栀梦 2020-12-09 14:29

From the docs:

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

相关标签:
4条回答
  • 2020-12-09 15:05

    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)
    }
    
    0 讨论(0)
  • 2020-12-09 15:14

    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
    
    0 讨论(0)
  • 2020-12-09 15:20

    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 :newTypeEncoder:

    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 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))       // Complex64
            json.Marshal(make(chan struct{})) // Chan
            json.Marshal(func() {})           // Func
    
    0 讨论(0)
  • 2020-12-09 15:21

    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.

    0 讨论(0)
提交回复
热议问题