85

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.

Michael Whatcott
  • 5,603
  • 6
  • 36
  • 50
  • 2
    I'd recommend revising your question slightly because I believe you're looking for a feature like `PreserveReferencesHandling` in newtonsoft's json.NET library (C#) however, I could correctly answer your question by unmarshalling something like `{"name":"evan","age":26}` because the last paragraph of your question isn't very specific. From that quote, it sounds like you need to implement `UnmarshalJSON` for your type. To be clear, you want to do this in Go; http://stackoverflow.com/questions/17818386/how-to-serialize-as-json-an-object-structure-with-circular-references – evanmcdonnal Nov 24 '15 at 20:54
  • 3
    Marshal a type which implements json.Marshaler by bringing its own MarshalJSON method and errors always. Dead simple. Absolutely no input(s) used. – Volker Nov 24 '15 at 20:56
  • Yes, thanks @Volker -- that's what I should have done. #facepalm – Michael Whatcott Nov 24 '15 at 20:59
  • Not sure why this hase been downvotes... @Volker - I'd gladly mark your comment as the accepted answer. – Michael Whatcott Nov 24 '15 at 21:09
  • @JimB - I'm not unmarshalling raw json into a structure. I'm marshalling a structure to json and handling the potential error. – Michael Whatcott Nov 24 '15 at 23:30
  • Yes sorry, completely misread. – JimB Nov 24 '15 at 23:48
  • 2
    To answer directly your question, if you pass channels or functions to the Marshal function you'll get a json.UnsupportedTypeError and if you pass a struct with a type that has value math.Inf or math.NaN you'll get a json.UnsupportedValueError – hbejgel Nov 25 '15 at 12:52
  • 1
    Oh, if only you had submitted your comment as an actual answer @hbejgel... – Michael Whatcott Nov 27 '15 at 19:23

5 Answers5

63

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
hbejgel
  • 4,674
  • 2
  • 17
  • 27
  • 7
    Could this then be interpreted as "as long as the type doesn't change" the `err` can safely be ignored? – Pylinux Feb 12 '20 at 08:47
39

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)
}
Michael Whatcott
  • 5,603
  • 6
  • 36
  • 50
Jonathan Oliver
  • 5,207
  • 32
  • 31
16

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
AnonymousX
  • 638
  • 7
  • 8
2

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

}

Shriprasad
  • 51
  • 6
1

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.

Denis V
  • 3,290
  • 1
  • 28
  • 40