Skip to content

Commit

Permalink
Expand support for legacy error handling
Browse files Browse the repository at this point in the history
WARNING: This commit contains breaking changes.

The IgnoreStructErrors and ReportLegacyErrorValues options
are deleted and folded into a unified ReportErrorsWithLegacySemantics
option that handles multiple legacy behaviors regarding errors.

In particular:
* Fully validate the syntactic structure of the JSON value
  before attempting any form of semantic unmarshaling.
* Continue unmarshaling even when encountering an error,
  and report the first error.
  • Loading branch information
dsnet committed Jan 2, 2025
1 parent 00ed864 commit eb5656e
Show file tree
Hide file tree
Showing 24 changed files with 312 additions and 264 deletions.
20 changes: 14 additions & 6 deletions arshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func Marshal(in any, opts ...Options) (out []byte, err error) {
xe := export.Encoder(enc)
xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
err = marshalEncode(enc, in, &xe.Struct)
if err != nil && xe.Flags.Get(jsonflags.ReportLegacyErrorValues) {
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return nil, internal.TransformMarshalError(in, err)
}
return bytes.Clone(xe.Buf), err
Expand All @@ -180,7 +180,7 @@ func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
xe := export.Encoder(enc)
xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
err = marshalEncode(enc, in, &xe.Struct)
if err != nil && xe.Flags.Get(jsonflags.ReportLegacyErrorValues) {
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformMarshalError(in, err)
}
return err
Expand All @@ -198,7 +198,7 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
xe := export.Encoder(out)
mo.CopyCoderOptions(&xe.Struct)
err = marshalEncode(out, in, mo)
if err != nil && xe.Flags.Get(jsonflags.ReportLegacyErrorValues) {
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformMarshalError(in, err)
}
return err
Expand Down Expand Up @@ -392,7 +392,7 @@ func Unmarshal(in []byte, out any, opts ...Options) (err error) {
defer export.PutBufferedDecoder(dec)
xd := export.Decoder(dec)
err = unmarshalFull(dec, out, &xd.Struct)
if err != nil && xd.Flags.Get(jsonflags.ReportLegacyErrorValues) {
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err)
}
return err
Expand All @@ -409,7 +409,7 @@ func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
defer export.PutStreamingDecoder(dec)
xd := export.Decoder(dec)
err = unmarshalFull(dec, out, &xd.Struct)
if err != nil && xd.Flags.Get(jsonflags.ReportLegacyErrorValues) {
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err)
}
return err
Expand Down Expand Up @@ -441,7 +441,7 @@ func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error)
xd := export.Decoder(in)
uo.CopyCoderOptions(&xd.Struct)
err = unmarshalDecode(in, out, uo)
if err != nil && uo.Flags.Get(jsonflags.ReportLegacyErrorValues) {
if err != nil && uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err)
}
return err
Expand All @@ -455,6 +455,14 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err er
va := addressableValue{v.Elem(), false} // dereferenced pointer is always addressable
t := va.Type()

// In legacy semantics, the entirety of the next JSON value
// was validated before attempting to unmarshal it.
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err := export.Decoder(in).CheckNextValue(); err != nil {
return err
}
}

// Lookup and call the unmarshal function for this type.
unmarshal := lookupArshaler(t).unmarshal
if uo.Unmarshalers != nil {
Expand Down
21 changes: 15 additions & 6 deletions arshal_any.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package json

import (
"cmp"
"reflect"
"strconv"

Expand Down Expand Up @@ -171,6 +172,7 @@ func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]
if !uo.Flags.Get(jsonflags.AllowInvalidUTF8) {
xd.Tokens.Last.DisableNamespace()
}
var errUmmarshal error
for dec.PeekKind() != '}' {
tok, err := dec.ReadToken()
if err != nil {
Expand All @@ -189,15 +191,18 @@ func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]
val, err := unmarshalValueAny(dec, uo)
obj[name] = val
if err != nil {
return obj, err
if isFatalError(err, uo.Flags) {
return obj, err
}
errUmmarshal = cmp.Or(err, errUmmarshal)
}
}
if _, err := dec.ReadToken(); err != nil {
return obj, err
}
return obj, nil
return obj, errUmmarshal
}
return nil, newUnmarshalErrorAfter(dec, mapStringAnyType, nil)
return nil, newUnmarshalErrorAfterWithSkipping(dec, uo, mapStringAnyType, nil)
}

func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) error {
Expand Down Expand Up @@ -252,17 +257,21 @@ func unmarshalArrayAny(dec *jsontext.Decoder, uo *jsonopts.Struct) ([]any, error
return nil, nil
case '[':
arr := []any{}
var errUmmarshal error
for dec.PeekKind() != ']' {
val, err := unmarshalValueAny(dec, uo)
arr = append(arr, val)
if err != nil {
return arr, err
if isFatalError(err, uo.Flags) {
return arr, err
}
errUmmarshal = cmp.Or(errUmmarshal, err)
}
}
if _, err := dec.ReadToken(); err != nil {
return arr, err
}
return arr, nil
return arr, errUmmarshal
}
return nil, newUnmarshalErrorAfter(dec, sliceAnyType, nil)
return nil, newUnmarshalErrorAfterWithSkipping(dec, uo, sliceAnyType, nil)
}
Loading

0 comments on commit eb5656e

Please sign in to comment.