Skip to content

proposal: encoding/json: include field name in unmarshal error messages for types that implement UnmarshalJSON #71958

Closed as duplicate of#58655
@MadLittleMods

Description

@MadLittleMods

Proposal Details

Problem context

The following example just outputs panic: unhelpful error which isn't useful in finding out which key in the JSON caused the problem, i.e "was it bar or baz that caused the problem?" We only have access to the value bytes (no key name) so even if we wanted to write a better error message, we couldn't.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
)

type strawberry struct{}

func (b *strawberry) UnmarshalJSON(data []byte) error {
	return errors.New("unhelpful error")
}

type myType struct {
	Foo string     `json:"foo,omitempty"`
	Bar strawberry `json:"bar,omitempty"`
	Baz strawberry `json:"baz,omitempty"`
}

func main() {
	json_bytes := []byte(`{ "foo": "test", "bar": 1, "baz": 2 }`)
	var data myType
	if err := json.Unmarshal(json_bytes, &data); err != nil {
		panic(err)
	}
	fmt.Println(data)
}

Compare this with the built-in types where we get some helpful output -> panic: json: cannot unmarshal number into Go struct field myType.bar of type string because of the automatic logic behind json.UnmarshalTypeError (see addErrorContext).

  • Playground link
  • Output: panic: json: cannot unmarshal number into Go struct field myType.bar of type string
package main

import (
	"encoding/json"
	"fmt"
)

type myType struct {
	Foo string `json:"foo,omitempty"`
	Bar string `json:"bar,omitempty"`
	Baz string `json:"baz,omitempty"`
}

func main() {
	json_bytes := []byte(`{ "foo": "test", "bar": 1, "baz": 2 }`)
	var data myType
	if err := json.Unmarshal(json_bytes, &data); err != nil {
		panic(err)
	}
	fmt.Println(data)
}

We can partially get our desired effect by returning json.UnmarshalTypeError ourselves but it doesn't allow for any extra context on why we failed to parse it. For example, what if the reason we failed to unmarshal was because Strawberries have at-least 200 seeds but there is not place to add this context with the current json.UnmarshalTypeError.

  • Playground link
  • Output: panic: json: cannot unmarshal 1 into Go struct field myType.bar of type main.strawberry
package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

type strawberry struct{}

func (b *strawberry) UnmarshalJSON(data []byte) error {
	return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf((*strawberry)(nil)).Elem(), Offset: 0}
}

type myType struct {
	Foo string     `json:"foo,omitempty"`
	Bar strawberry `json:"bar,omitempty"`
	Baz strawberry `json:"baz,omitempty"`
}

func main() {
	json_bytes := []byte(`{ "foo": "test", "bar": 1, "baz": 2 }`)
	var data myType
	if err := json.Unmarshal(json_bytes, &data); err != nil {
		panic(err)
	}
	fmt.Println(data)
}

Proposal

Wrap any error from UnmarshalJSON and include the field name context that generated the error.

Another possibility is to extend json.UnmarshalTypeError to allow for additional custom error context which would work but not everyone is going to be as meticulous to always return json.UnmarshalTypeError to get the benefits.

I'm not writing Go everyday so there is probably a better strategy as well.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions