diff --git a/arshal.go b/arshal.go index 91b117c..5fed6e3 100644 --- a/arshal.go +++ b/arshal.go @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 { diff --git a/arshal_any.go b/arshal_any.go index 6ba447c..6c2e8aa 100644 --- a/arshal_any.go +++ b/arshal_any.go @@ -5,6 +5,7 @@ package json import ( + "cmp" "reflect" "strconv" @@ -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 { @@ -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 { @@ -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) } diff --git a/arshal_default.go b/arshal_default.go index 3bc580b..c3d2a81 100644 --- a/arshal_default.go +++ b/arshal_default.go @@ -6,6 +6,7 @@ package json import ( "bytes" + "cmp" "encoding" "encoding/base32" "encoding/base64" @@ -124,7 +125,7 @@ func makeBoolArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } // Optimize for marshaling without preceding whitespace. @@ -149,7 +150,7 @@ func makeBoolArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } tok, err := dec.ReadToken() if err != nil { @@ -186,7 +187,7 @@ func makeBoolArshaler(t reflect.Type) *arshaler { return nil } } - return newUnmarshalErrorAfter(dec, t, nil) + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) } return &fncs } @@ -196,7 +197,7 @@ func makeStringArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } // Optimize for marshaling without preceding whitespace or string escaping. @@ -231,7 +232,7 @@ func makeStringArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } var flags jsonwire.ValueFlags val, err := xd.ReadValue(&flags) @@ -321,7 +322,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { mo.Format = "" return marshalArray(enc, va, mo) default: - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } } else if mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && (va.Kind() == reflect.Array || hasMarshaler) { @@ -358,7 +359,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { uo.Format = "" return unmarshalArray(dec, va, uo) default: - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } } else if uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && (va.Kind() == reflect.Array || dec.PeekKind() == '[') { @@ -425,7 +426,7 @@ func makeIntArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } // Optimize for marshaling without preceding whitespace or string escaping. @@ -446,7 +447,7 @@ func makeIntArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) var flags jsonwire.ValueFlags @@ -512,7 +513,7 @@ func makeUintArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } // Optimize for marshaling without preceding whitespace or string escaping. @@ -533,7 +534,7 @@ func makeUintArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) var flags jsonwire.ValueFlags @@ -594,7 +595,7 @@ func makeFloatArshaler(t reflect.Type) *arshaler { if mo.Format == "nonfinite" { allowNonFinite = true } else { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } } @@ -629,7 +630,7 @@ func makeFloatArshaler(t reflect.Type) *arshaler { if uo.Format == "nonfinite" { allowNonFinite = true } else { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } } stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) @@ -729,7 +730,7 @@ func makeMapArshaler(t reflect.Type) *arshaler { emitNull = false mo.Format = "" default: - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } } @@ -874,7 +875,7 @@ func makeMapArshaler(t reflect.Type) *arshaler { case "emitnull", "emitempty": uo.Format = "" // only relevant for marshaling default: - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } } tok, err := dec.ReadToken() @@ -923,6 +924,7 @@ func makeMapArshaler(t reflect.Type) *arshaler { seen = reflect.MakeMap(reflect.MapOf(k.Type(), emptyStructType)) } + var errUnmarshal error for dec.PeekKind() != '}' { k.SetZero() flagsOriginal := uo.Flags @@ -930,11 +932,21 @@ func makeMapArshaler(t reflect.Type) *arshaler { err := unmarshalKey(dec, k, uo) uo.Flags = flagsOriginal if err != nil { - return err + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } if k.Kind() == reflect.Interface && !k.IsNil() && !k.Elem().Type().Comparable() { - err := fmt.Errorf("invalid incomparable key type %v", k.Elem().Type()) - return newUnmarshalErrorAfter(dec, t, err) + err := newUnmarshalErrorAfter(dec, t, fmt.Errorf("invalid incomparable key type %v", k.Elem().Type())) + if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err + } + if err2 := dec.SkipValue(); err2 != nil { + return err2 + } + errUnmarshal = cmp.Or(errUnmarshal, err) + continue } if v2 := va.MapIndex(k.Value); v2.IsValid() { @@ -957,15 +969,18 @@ func makeMapArshaler(t reflect.Type) *arshaler { seen.SetMapIndex(k.Value, reflect.Zero(emptyStructType)) } if err != nil { - return err + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } } if _, err := dec.ReadToken(); err != nil { return err } - return nil + return errUnmarshal } - return newUnmarshalErrorAfter(dec, t, nil) + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) } return &fncs } @@ -1010,10 +1025,10 @@ func makeStructArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } once.Do(init) - if errInit != nil && !mo.Flags.Get(jsonflags.IgnoreStructErrors) { + if errInit != nil && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return newMarshalErrorBefore(enc, errInit.GoType, errInit.Err) } if err := enc.WriteToken(jsontext.ObjectStart); err != nil { @@ -1172,7 +1187,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } tok, err := dec.ReadToken() if err != nil { @@ -1187,11 +1202,12 @@ func makeStructArshaler(t reflect.Type) *arshaler { return nil case '{': once.Do(init) - if errInit != nil && !uo.Flags.Get(jsonflags.IgnoreStructErrors) { + if errInit != nil && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return newUnmarshalErrorAfter(dec, errInit.GoType, errInit.Err) } var seenIdxs uintSet xd.Tokens.Last.DisableNamespace() + var errUnmarshal error for dec.PeekKind() != '}' { // Process the object member name. var flags jsonwire.ValueFlags @@ -1210,7 +1226,11 @@ func makeStructArshaler(t reflect.Type) *arshaler { } if f == nil { if uo.Flags.Get(jsonflags.RejectUnknownMembers) && (fields.inlinedFallback == nil || fields.inlinedFallback.unknown) { - return newUnmarshalErrorAfter(dec, t, ErrUnknownName) + err := newUnmarshalErrorAfter(dec, t, ErrUnknownName) + if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } if !uo.Flags.Get(jsonflags.AllowDuplicateNames) && !xd.Namespaces.Last().InsertUnquoted(name) { // TODO: Unread the object name. @@ -1225,7 +1245,10 @@ func makeStructArshaler(t reflect.Type) *arshaler { } else { // Marshal into value capable of storing arbitrary object members. if err := unmarshalInlinedFallbackNext(dec, va, uo, fields.inlinedFallback, val, name); err != nil { - return err + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } } continue @@ -1257,22 +1280,32 @@ func makeStructArshaler(t reflect.Type) *arshaler { if len(f.index) > 1 { v = v.fieldByIndex(f.index[1:], true) if !v.IsValid() { - return newUnmarshalErrorBefore(dec, t, errNilField) + err := newUnmarshalErrorBefore(dec, t, errNilField) + if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + unmarshal = func(dec *jsontext.Decoder, _ addressableValue, _ *jsonopts.Struct) error { + return dec.SkipValue() + } } } err = unmarshal(dec, v, uo) uo.Flags = flagsOriginal uo.Format = "" if err != nil { - return err + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } } if _, err := dec.ReadToken(); err != nil { return err } - return nil + return errUnmarshal } - return newUnmarshalErrorAfter(dec, t, nil) + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) } return &fncs } @@ -1369,7 +1402,7 @@ func makeSliceArshaler(t reflect.Type) *arshaler { emitNull = false mo.Format = "" default: - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } } @@ -1417,7 +1450,7 @@ func makeSliceArshaler(t reflect.Type) *arshaler { case "emitnull", "emitempty": uo.Format = "" // only relevant for marshaling default: - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } } @@ -1442,6 +1475,7 @@ func makeSliceArshaler(t reflect.Type) *arshaler { va.SetLen(cap) } var i int + var errUnmarshal error for dec.PeekKind() != ']' { if i == cap { va.Value.Grow(1) @@ -1455,8 +1489,11 @@ func makeSliceArshaler(t reflect.Type) *arshaler { v.SetZero() } if err := unmarshal(dec, v, uo); err != nil { - va.SetLen(i) - return err + if isFatalError(err, uo.Flags) { + va.SetLen(i) + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } } if i == 0 { @@ -1467,9 +1504,9 @@ func makeSliceArshaler(t reflect.Type) *arshaler { if _, err := dec.ReadToken(); err != nil { return err } - return nil + return errUnmarshal } - return newUnmarshalErrorAfter(dec, t, nil) + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) } return &fncs } @@ -1490,7 +1527,7 @@ func makeArrayArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } once.Do(init) if err := enc.WriteToken(jsontext.ArrayStart); err != nil { @@ -1514,7 +1551,7 @@ func makeArrayArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } tok, err := dec.ReadToken() if err != nil { @@ -1534,6 +1571,7 @@ func makeArrayArshaler(t reflect.Type) *arshaler { unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t.Elem()) } var i int + var errUnmarshal error for dec.PeekKind() != ']' { if i >= n { if err := dec.SkipValue(); err != nil { @@ -1547,7 +1585,10 @@ func makeArrayArshaler(t reflect.Type) *arshaler { v.SetZero() } if err := unmarshal(dec, v, uo); err != nil { - return err + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) } i++ } @@ -1561,9 +1602,9 @@ func makeArrayArshaler(t reflect.Type) *arshaler { if err != nil && !uo.Flags.Get(jsonflags.UnmarshalArrayFromAnyLength) { return newUnmarshalErrorAfter(dec, t, err) } - return nil + return errUnmarshal } - return newUnmarshalErrorAfter(dec, t, nil) + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) } return &fncs } @@ -1655,7 +1696,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } if va.IsNil() { return enc.WriteToken(jsontext.Null) @@ -1695,7 +1736,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } if uo.Flags.Get(jsonflags.MergeWithLegacySemantics) && !va.IsNil() { // Legacy merge behavior is difficult to explain. @@ -1744,7 +1785,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { k := dec.PeekKind() if !isAnyType(t) { - return newUnmarshalErrorBefore(dec, t, errNilInterface) + return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errNilInterface) } switch k { case 'f', 't': diff --git a/arshal_funcs.go b/arshal_funcs.go index d1ba39c..4b66bca 100644 --- a/arshal_funcs.go +++ b/arshal_funcs.go @@ -178,14 +178,14 @@ func MarshalFuncV1[T any](fn func(T) ([]byte, error)) *Marshalers { val, err := fn(va.castTo(t).Interface().(T)) if err != nil { err = wrapSkipFunc(err, "marshal function of type func(T) ([]byte, error)") - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFuncV1") // unlike unmarshal, always wrapped } err = newMarshalErrorBefore(enc, t, err) return collapseSemanticErrors(err) } if err := enc.WriteValue(val); err != nil { - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFuncV1") // unlike unmarshal, always wrapped } if isSyntacticError(err) { @@ -233,7 +233,7 @@ func MarshalFuncV2[T any](fn func(*jsontext.Encoder, T, Options) error) *Marshal } err = errSkipMutation } - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFuncV2") // unlike unmarshal, always wrapped } if !export.IsIOError(err) { @@ -270,7 +270,7 @@ func UnmarshalFuncV1[T any](fn func([]byte, T) error) *Unmarshalers { err = fn(val, va.castTo(t).Interface().(T)) if err != nil { err = wrapSkipFunc(err, "unmarshal function of type func([]byte, T) error") - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } err = newUnmarshalErrorAfter(dec, t, err) @@ -315,7 +315,10 @@ func UnmarshalFuncV2[T any](fn func(*jsontext.Decoder, T, Options) error) *Unmar } err = errSkipMutation } - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { + return err2 + } return err // unlike marshal, never wrapped } if !isSyntacticError(err) && !export.IsIOError(err) { diff --git a/arshal_inlined.go b/arshal_inlined.go index 7dba1ac..6f7fcf2 100644 --- a/arshal_inlined.go +++ b/arshal_inlined.go @@ -186,7 +186,7 @@ func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *b = append(*b, ',') } } else { - return newUnmarshalErrorAfter(dec, v.Type(), errRawInlinedNotObject) + return newUnmarshalErrorAfterWithSkipping(dec, uo, v.Type(), errRawInlinedNotObject) } } *b = append(*b, quotedName...) diff --git a/arshal_methods.go b/arshal_methods.go index 7b9f33d..1c1a3e6 100644 --- a/arshal_methods.go +++ b/arshal_methods.go @@ -127,7 +127,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { return append(b, b2...), err }); err != nil { err = wrapSkipFunc(err, "marshal method") - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped } if !isSemanticError(err) && !export.IsIOError(err) { @@ -165,7 +165,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { appender := va.Addr().Interface().(encodingTextAppender) if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil { err = wrapSkipFunc(err, "append method") - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped } if !isSemanticError(err) && !export.IsIOError(err) { @@ -189,14 +189,14 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { val, err := marshaler.MarshalJSON() if err != nil { err = wrapSkipFunc(err, "marshal method") - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped } err = newMarshalErrorBefore(enc, t, err) return collapseSemanticErrors(err) } if err := enc.WriteValue(val); err != nil { - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped } if isSyntacticError(err) { @@ -227,7 +227,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { } if err != nil { err = wrapSkipFunc(err, "marshal method") - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONV2") // unlike unmarshal, always wrapped } if !export.IsIOError(err) { @@ -261,7 +261,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler) if err := unmarshaler.UnmarshalText(s); err != nil { err = wrapSkipFunc(err, "unmarshal method") - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } if !isSemanticError(err) && !isSyntacticError(err) && !export.IsIOError(err) { @@ -288,7 +288,7 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { unmarshaler := va.Addr().Interface().(UnmarshalerV1) if err := unmarshaler.UnmarshalJSON(val); err != nil { err = wrapSkipFunc(err, "unmarshal method") - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } err = newUnmarshalErrorAfter(dec, t, err) @@ -317,7 +317,10 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { } if err != nil { err = wrapSkipFunc(err, "unmarshal method") - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { + return err2 + } return err // unlike marshal, never wrapped } if !isSyntacticError(err) && !export.IsIOError(err) { diff --git a/arshal_test.go b/arshal_test.go index cb1c140..dea8e6b 100644 --- a/arshal_test.go +++ b/arshal_test.go @@ -2936,7 +2936,7 @@ func TestMarshal(t *testing.T) { wantErr: EM(errors.New("embedded Go struct field NamedString of non-struct type must be explicitly given a JSON name")).withType(0, T[structExportedEmbedded]()), }, { name: jsontest.Name("Structs/Valid/ExportedEmbedded"), - opts: []Options{jsonflags.IgnoreStructErrors | 1}, + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, in: structExportedEmbedded{"hello"}, want: `{"NamedString":"hello"}`, }, { @@ -2950,7 +2950,7 @@ func TestMarshal(t *testing.T) { wantErr: EM(errors.New("embedded Go struct field namedString of non-struct type must be explicitly given a JSON name")).withType(0, T[structUnexportedEmbedded]()), }, { name: jsontest.Name("Structs/Valid/UnexportedEmbedded"), - opts: []Options{jsonflags.IgnoreStructErrors | 1}, + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, in: structUnexportedEmbedded{}, want: `{}`, }, { @@ -2959,12 +2959,12 @@ func TestMarshal(t *testing.T) { wantErr: EM(errors.New("Go struct field namedString is not exported")).withType(0, T[structUnexportedEmbeddedTag]()), }, { name: jsontest.Name("Structs/Valid/UnexportedEmbeddedTag"), - opts: []Options{jsonflags.IgnoreStructErrors | 1}, + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, in: structUnexportedEmbeddedTag{}, want: `{}`, }, { name: jsontest.Name("Structs/Invalid/UnexportedEmbeddedMethodTag"), - opts: []Options{jsonflags.IgnoreStructErrors | 1}, + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, in: structUnexportedEmbeddedMethodTag{}, want: `{}`, }, { @@ -7181,7 +7181,7 @@ func TestUnmarshal(t *testing.T) { wantErr: EU(errors.New("embedded Go struct field NamedString of non-struct type must be explicitly given a JSON name")).withType('{', T[structExportedEmbedded]()), }, { name: jsontest.Name("Structs/Valid/ExportedEmbedded"), - opts: []Options{jsonflags.IgnoreStructErrors | 1}, + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, inBuf: `{"NamedString":"hello"}`, inVal: addr(structExportedEmbedded{}), want: addr(structExportedEmbedded{"hello"}), diff --git a/arshal_time.go b/arshal_time.go index fcc0439..04a4887 100644 --- a/arshal_time.go +++ b/arshal_time.go @@ -46,7 +46,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { var m durationArshaler if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { if !m.initFormat(mo.Format) { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } } else if mo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) { return marshalNano(enc, va, mo) @@ -69,7 +69,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { var u durationArshaler if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { if !u.initFormat(uo.Format) { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } } else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) { return unmarshalNano(dec, va, uo) @@ -116,7 +116,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { var m timeArshaler if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { if !m.initFormat(mo.Format) { - return newInvalidFormatError(enc, t, mo.Format) + return newInvalidFormatError(enc, t, mo) } } @@ -124,7 +124,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { m.tt = *va.Addr().Interface().(*time.Time) k := stringOrNumberKind(!m.isNumeric() || mo.Flags.Get(jsonflags.StringifyNumbers)) if err := xe.AppendRaw(k, !m.hasCustomFormat(), m.appendMarshal); err != nil { - if mo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped } if !isSyntacticError(err) && !export.IsIOError(err) { @@ -139,7 +139,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { var u timeArshaler if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { if !u.initFormat(uo.Format) { - return newInvalidFormatError(dec, t, uo.Format) + return newInvalidFormatError(dec, t, uo) } } else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) { u.looseRFC3339 = true @@ -163,7 +163,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { } val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) if err := u.unmarshal(val); err != nil { - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } return newUnmarshalErrorAfter(dec, t, err) @@ -175,7 +175,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { break } if err := u.unmarshal(val); err != nil { - if uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return err // unlike marshal, never wrapped } return newUnmarshalErrorAfter(dec, t, err) diff --git a/errors.go b/errors.go index 3e3d95a..ffd0716 100644 --- a/errors.go +++ b/errors.go @@ -13,6 +13,8 @@ import ( "strings" "sync" + "github.com/go-json-experiment/json/internal/jsonflags" + "github.com/go-json-experiment/json/internal/jsonopts" "github.com/go-json-experiment/json/internal/jsonwire" "github.com/go-json-experiment/json/jsontext" ) @@ -46,6 +48,13 @@ func isSyntacticError(err error) bool { return ok } +// isFatalError reports whether this error must terminate asharling. +// Syntactic errors and I/O error are considered fatal. +func isFatalError(err error, flags jsonflags.Flags) bool { + return !flags.Get(jsonflags.ReportErrorsWithLegacySemantics) || + isSyntacticError(err) || export.IsIOError(err) +} + // SemanticError describes an error determining the meaning // of JSON data as Go data or vice-versa. // @@ -78,14 +87,19 @@ type SemanticError struct { type coder interface{ StackPointer() jsontext.Pointer } // newInvalidFormatError wraps err in a SemanticError because -// the current type t cannot handle the provided format flag. -func newInvalidFormatError(c coder, t reflect.Type, format string) error { - err := fmt.Errorf("invalid format flag %q", format) +// the current type t cannot handle the provided options format. +// This error must be called before producing or consuming the next value. +// +// If [jsonflags.ReportErrorsWithLegacySemantics] is specified, +// then this automatically skips the next value when unmarshaling +// to ensure that the value is fully consumed. +func newInvalidFormatError(c coder, t reflect.Type, o *jsonopts.Struct) error { + err := fmt.Errorf("invalid format flag %q", o.Format) switch c := c.(type) { case *jsontext.Encoder: err = newMarshalErrorBefore(c, t, err) case *jsontext.Decoder: - err = newUnmarshalErrorBefore(c, t, err) + err = newUnmarshalErrorBeforeWithSkipping(c, o, t, err) } return err } @@ -108,6 +122,19 @@ func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) err JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1))} } +// newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore], +// but automatically skips the the next value if +// [jsonflags.ReportErrorsWithLegacySemantics] is specified. +func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error { + err = newUnmarshalErrorBefore(d, t, err) + if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := export.Decoder(d).SkipValue(); err2 != nil { + return err2 + } + } + return err +} + // newUnmarshalErrorAfter wraps err in a SemanticError assuming that d // is positioned right after the previous token or value, which caused an error. func newUnmarshalErrorAfter(d *jsontext.Decoder, t reflect.Type, err error) error { @@ -129,6 +156,19 @@ func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err er return serr } +// newUnmarshalErrorAfterWithSkipping is like [newUnmarshalErrorAfter], +// but automatically skips the remainder of the value if +// [jsonflags.ReportErrorsWithLegacySemantics] is specified. +func newUnmarshalErrorAfterWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error { + err = newUnmarshalErrorAfter(d, t, err) + if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := export.Decoder(d).SkipValueRemainder(); err2 != nil { + return err2 + } + } + return err +} + // newSemanticErrorWithPosition wraps err in a SemanticError assuming that // the error occurred at the provided depth, and length. // If err is already a SemanticError, then position information is only diff --git a/internal/jsonflags/flags.go b/internal/jsonflags/flags.go index 6fd1972..9e10fae 100644 --- a/internal/jsonflags/flags.go +++ b/internal/jsonflags/flags.go @@ -64,7 +64,7 @@ const ( MergeWithLegacySemantics | OmitEmptyWithLegacyDefinition | RejectFloatOverflow | - ReportLegacyErrorValues | + ReportErrorsWithLegacySemantics | StringifyWithLegacySemantics | UnmarshalArrayFromAnyLength @@ -123,19 +123,19 @@ const ( const ( _ Bools = (maxArshalV2Flag >> 1) << iota - CallMethodsWithLegacySemantics // marshal or unmarshal - FormatBytesWithLegacySemantics // marshal or unmarshal - FormatTimeWithLegacySemantics // marshal or unmarshal - IgnoreStructErrors // marshal or unmarshal - MatchCaseSensitiveDelimiter // marshal or unmarshal - MergeWithLegacySemantics // unmarshal - OmitEmptyWithLegacyDefinition // marshal - RejectFloatOverflow // unmarshal - ReportLegacyErrorValues // marshal or unmarshal - StringifyWithLegacySemantics // marshal or unmarshal - StringifyBoolsAndStrings // marshal or unmarshal; for internal use by jsonv2.makeStructArshaler - UnmarshalAnyWithRawNumber // unmarshal; for internal use by jsonv1.Decoder.UseNumber - UnmarshalArrayFromAnyLength // unmarshal + CallMethodsWithLegacySemantics // marshal or unmarshal + FormatBytesWithLegacySemantics // marshal or unmarshal + FormatTimeWithLegacySemantics // marshal or unmarshal + IgnoreStructErrors // marshal or unmarshal + MatchCaseSensitiveDelimiter // marshal or unmarshal + MergeWithLegacySemantics // unmarshal + OmitEmptyWithLegacyDefinition // marshal + RejectFloatOverflow // unmarshal + ReportErrorsWithLegacySemantics // marshal or unmarshal + StringifyWithLegacySemantics // marshal or unmarshal + StringifyBoolsAndStrings // marshal or unmarshal; for internal use by jsonv2.makeStructArshaler + UnmarshalAnyWithRawNumber // unmarshal; for internal use by jsonv1.Decoder.UseNumber + UnmarshalArrayFromAnyLength // unmarshal maxArshalV1Flag ) diff --git a/internal/jsonflags/flags_test.go b/internal/jsonflags/flags_test.go index 1673f71..5aa1067 100644 --- a/internal/jsonflags/flags_test.go +++ b/internal/jsonflags/flags_test.go @@ -43,9 +43,9 @@ func TestFlags(t *testing.T) { Check{want: Flags{Presence: uint64(Multiline | AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, Clear{in: AllowDuplicateNames | AllowInvalidUTF8}, Check{want: Flags{Presence: uint64(Multiline), Values: uint64(0)}}, - Set{in: AllowInvalidUTF8 | Deterministic | IgnoreStructErrors | 1}, + Set{in: AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | 1}, Set{in: Multiline | StringifyNumbers | RejectFloatOverflow | 0}, - Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Deterministic | IgnoreStructErrors | Multiline | StringifyNumbers | RejectFloatOverflow), Values: uint64(AllowInvalidUTF8 | Deterministic | IgnoreStructErrors)}}, + Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | Multiline | StringifyNumbers | RejectFloatOverflow), Values: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics)}}, Clear{in: ^AllCoderFlags}, Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Multiline), Values: uint64(AllowInvalidUTF8)}}, } diff --git a/jsontext/decode.go b/jsontext/decode.go index 178ceec..73b308b 100644 --- a/jsontext/decode.go +++ b/jsontext/decode.go @@ -400,6 +400,36 @@ func (d *decoderState) SkipValue() error { } } +func (d *Decoder) SkipValueRemainder() error { + return d.s.SkipValueRemainder() // MUSTDO: Remove +} + +// SkipValueRemainder skips the remainder of a value after reading a +// '{' or '[' token. +func (d *decoderState) SkipValueRemainder() error { + // Check whether the last token read was a '{' or '['. + // If so, skip all tokens until we pop the stack. + if d.Tokens.Depth()-1 > 0 && d.Tokens.Last.Length() == 0 { + for n := d.Tokens.Depth(); d.Tokens.Depth() >= n; { + if _, err := d.ReadToken(); err != nil { + return err + } + } + } + return nil +} + +// SkipUntil skips all tokens until the state machine is at or past +// the specified depth and length. +func (d *decoderState) SkipUntil(depth int, length int64) error { + for d.Tokens.Depth() > depth || (d.Tokens.Depth() == depth && d.Tokens.Last.Length() < length) { + if _, err := d.ReadToken(); err != nil { + return err + } + } + return nil +} + // ReadToken reads the next [Token], advancing the read offset. // The returned token is only valid until the next Peek, Read, or Skip call. // It returns [io.EOF] if there are no more tokens. @@ -712,6 +742,23 @@ func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) { return d.buf[pos-n : pos : pos], nil } +// CheckNextValue checks whether the next value is syntactically valid, +// but does not advance the read offset. +func (d *decoderState) CheckNextValue() error { + d.PeekKind() // populates d.peekPos and d.peekErr + pos, err := d.peekPos, d.peekErr + d.peekPos, d.peekErr = 0, nil + if err != nil { + return err + } + + var flags jsonwire.ValueFlags + if pos, err := d.consumeValue(&flags, pos, d.Tokens.Depth()); err != nil { + return wrapSyntacticError(d, err, pos, +1) + } + return nil +} + // CheckEOF verifies that the input has no more data. func (d *decoderState) CheckEOF() error { switch pos, err := d.consumeWhitespace(d.prevEnd); err { diff --git a/v1/decode.go b/v1/decode.go index cafae67..5a3df7f 100644 --- a/v1/decode.go +++ b/v1/decode.go @@ -237,7 +237,7 @@ func (n *Number) UnmarshalJSONV2(dec *jsontext.Decoder, opts jsonv2.Options) err verbatim := jsonwire.ConsumeSimpleString(val) == len(val) val = jsonwire.UnquoteMayCopy(val, verbatim) if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil { - return &jsonv2.SemanticError{JSONKind: val0.Kind(), JSONValue: val0, GoType: numberType, Err: strconv.ErrSyntax} + return &jsonv2.SemanticError{JSONKind: val0.Kind(), JSONValue: val0.Clone(), GoType: numberType, Err: strconv.ErrSyntax} } *n = Number(val) return nil diff --git a/v1/decode_test.go b/v1/decode_test.go index b19c93e..a8277b7 100644 --- a/v1/decode_test.go +++ b/v1/decode_test.go @@ -1111,7 +1111,6 @@ func TestMarshalInvalidUTF8(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) got, err := Marshal(tt.in) if string(got) != tt.want || err != nil { t.Errorf("%s: Marshal(%q):\n\tgot: (%q, %v)\n\twant: (%q, nil)", tt.Where, tt.in, got, err, tt.want) @@ -1133,7 +1132,6 @@ func TestMarshalNumberZeroVal(t *testing.T) { } func TestMarshalEmbeds(t *testing.T) { - skipKnownFailure(t) top := &Top{ Level0: 1, Embed0: Embed0{ @@ -1204,7 +1202,6 @@ func equalError(a, b error) bool { func TestUnmarshal(t *testing.T) { for _, tt := range unmarshalTests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) in := []byte(tt.in) if err := checkValid(in); err != nil { if !equalError(err, tt.err) { @@ -1407,7 +1404,6 @@ func TestErrorMessageFromMisusedString(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) r := strings.NewReader(tt.in) var s WrongString err := NewDecoder(r).Decode(&s) @@ -1784,7 +1780,6 @@ func TestEmptyString(t *testing.T) { // Test that a null for ,string is not replaced with the previous quoted string (issue 7046). // It should also not be an error (issue 2540, issue 8587). func TestNullString(t *testing.T) { - skipKnownFailure(t) type T struct { A int `json:",string"` B int `json:",string"` @@ -1805,6 +1800,10 @@ func TestNullString(t *testing.T) { } } +func addr[T any](v T) *T { + return &v +} + func TestInterfaceSet(t *testing.T) { errUnmarshal := &UnmarshalTypeError{Value: "object", Offset: 5, Type: reflect.TypeFor[int](), Field: "X"} tests := []struct { @@ -1913,7 +1912,6 @@ type NullTest struct { // JSON null values should be ignored for primitives and string values instead of resulting in an error. // Issue 2540 func TestUnmarshalNulls(t *testing.T) { - skipKnownFailure(t) // Unmarshal docs: // The JSON null value unmarshals into an interface, map, pointer, or slice // by setting that Go value to nil. Because null is often used in JSON to mean @@ -2119,7 +2117,6 @@ func TestUnmarshalTypeError(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) err := Unmarshal([]byte(tt.in), tt.dest) if _, ok := err.(*UnmarshalTypeError); !ok { t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %T\n\twant: %T", @@ -2145,7 +2142,6 @@ func TestUnmarshalSyntax(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) var x any err := Unmarshal([]byte(tt.in), &x) if _, ok := err.(*SyntaxError); !ok { @@ -2167,7 +2163,6 @@ type unexportedFields struct { } func TestUnmarshalUnexported(t *testing.T) { - skipKnownFailure(t) input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` want := &unexportedFields{Name: "Bob"} @@ -2263,7 +2258,6 @@ func TestPrefilled(t *testing.T) { }} for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) ptrstr := fmt.Sprintf("%v", tt.ptr) err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here if err != nil { @@ -2293,7 +2287,6 @@ func TestInvalidUnmarshal(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) switch gotErr := Unmarshal([]byte(tt.in), tt.v); { case gotErr == nil: t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) @@ -2471,7 +2464,6 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { }} for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) err := Unmarshal([]byte(tt.in), tt.ptr) if !equalError(err, tt.err) { t.Errorf("%s: Unmarshal error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) @@ -2511,7 +2503,6 @@ func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { }} for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) dec := NewDecoder(strings.NewReader(tt.in)) var err error for err == nil { diff --git a/v1/diff_test.go b/v1/diff_test.go index 7f35171..cfe6202 100644 --- a/v1/diff_test.go +++ b/v1/diff_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package json +package json_test import ( "errors" @@ -15,6 +15,7 @@ import ( jsonv2 "github.com/go-json-experiment/json" "github.com/go-json-experiment/json/jsontext" + jsonv1 "github.com/go-json-experiment/json/v1" ) // NOTE: This file serves as a list of semantic differences between v1 and v2. @@ -26,7 +27,7 @@ var jsonPackages = []struct { Marshal func(any) ([]byte, error) Unmarshal func([]byte, any) error }{ - {"v1", Marshal, Unmarshal}, + {"v1", jsonv1.Marshal, jsonv1.Unmarshal}, {"v2", func(in any) ([]byte, error) { return jsonv2.Marshal(in) }, func(in []byte, out any) error { return jsonv2.Unmarshal(in, out) }}, @@ -370,7 +371,6 @@ func TestStringOption(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal/Null", json.Version), func(t *testing.T) { - skipKnownFailure(t) var got Types err := json.Unmarshal([]byte(`{ "Bool": "null", @@ -420,7 +420,6 @@ func TestStringOption(t *testing.T) { }) t.Run(path.Join("Unmarshal/Deep", json.Version), func(t *testing.T) { - skipKnownFailure(t) var got Types want := map[string]Types{ "v1": { @@ -636,7 +635,6 @@ func TestPointerReceiver(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { - skipKnownFailure(t) var cc CallCheck in := Values{ S: []CallCheck{cc}, @@ -661,7 +659,6 @@ func TestPointerReceiver(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - skipKnownFailure(t) in := `{"S":[""],"A":[""],"M":{"":""},"V":"","I":""}` called := CallCheck("CALLED") // resulting state if UnmarshalJSON is called want := map[string]Values{ @@ -888,7 +885,6 @@ func TestMergeNull(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - skipKnownFailure(t) // Start with a non-empty value where all fields are populated. in := Types{ Bool: true, @@ -968,7 +964,6 @@ func TestMergeComposite(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { - skipKnownFailure(t) // Start with a non-empty value where all fields are populated. in := Composites{ Slice: []Tuple{{Old: true}, {Old: true}}[:1], diff --git a/v1/encode_test.go b/v1/encode_test.go index 3ed847f..48c3b5d 100644 --- a/v1/encode_test.go +++ b/v1/encode_test.go @@ -328,7 +328,6 @@ type renamedByteSlice []byte type renamedRenamedByteSlice []renamedByte func TestEncodeRenamedByteSlice(t *testing.T) { - skipKnownFailure(t) s := renamedByteSlice("abc") got, err := Marshal(s) if err != nil { @@ -420,7 +419,6 @@ func TestUnsupportedValues(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) if _, err := Marshal(tt.in); err != nil { if _, ok := err.(*UnsupportedValueError); !ok { t.Errorf("%s: Marshal error:\n\tgot: %T\n\twant: %T", tt.Where, err, new(UnsupportedValueError)) @@ -718,7 +716,6 @@ func TestAnonymousFields(t *testing.T) { for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) b, err := Marshal(tt.makeInput()) if err != nil { t.Fatalf("%s: Marshal error: %v", tt.Where, err) @@ -797,7 +794,6 @@ func TestNilMarshal(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) switch got, err := Marshal(tt.in); { case err != nil: t.Fatalf("%s: Marshal error: %v", tt.Where, err) @@ -1103,7 +1099,6 @@ func TestTextMarshalerMapKeysAreSorted(t *testing.T) { // https://golang.org/issue/33675 func TestNilMarshalerTextMapKey(t *testing.T) { - skipKnownFailure(t) got, err := Marshal(map[*unmarshalerText]int{ (*unmarshalerText)(nil): 1, {"A", "B"}: 2, @@ -1313,7 +1308,6 @@ func TestMarshalRawMessageValue(t *testing.T) { for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) b, err := Marshal(tt.in) if ok := (err == nil); ok != tt.ok { if err != nil { diff --git a/v1/failing.txt b/v1/failing.txt deleted file mode 100644 index 93a8c1a..0000000 --- a/v1/failing.txt +++ /dev/null @@ -1,2 +0,0 @@ -TestStringOption -TestStringOption/Unmarshal/Deep/v1 \ No newline at end of file diff --git a/v1/failing_test.go b/v1/failing_test.go deleted file mode 100644 index 834536f..0000000 --- a/v1/failing_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package json - -import ( - _ "embed" - "flag" - "fmt" - "os" - "os/exec" - "slices" - "strings" - "sync" - "testing" -) - -var skipKnownFailures = flag.Bool("skip-known-failures", true, "skip tests that are known to already be failing") -var updateKnownFailures = flag.Bool("update-known-failures", false, "update the list of known failures") - -//go:embed failing.txt -var knownFailuresText string -var knownFailures = sync.OnceValue(func() map[string]bool { - failures := make(map[string]bool) - for _, s := range strings.Split(knownFailuresText, "\n") { - failures[strings.TrimRight(s, "\r")] = true - } - return failures -}) - -// skipKnownFailure skips the current test if it is in the failing.txt list. -func skipKnownFailure(t *testing.T) { - if *skipKnownFailures && knownFailures()[t.Name()] { - t.SkipNow() - } -} - -// TestKnownFailures tests whether the failing.old is up-to-date. -func TestKnownFailures(t *testing.T) { - if !*skipKnownFailures { - return // avoid infinite recursion calling the same test - } - - // Produce a sorted list of currently known failures. - b, _ := exec.Command("go", "test", "-skip-known-failures=false", ".").CombinedOutput() - var newFailing []string - for _, line := range strings.Split(string(b), "\n") { - if _, suffix, ok := strings.Cut(strings.TrimRight(line, "\r"), "--- FAIL: "); ok { - suffix = strings.TrimSuffix(suffix, ")") - suffix = strings.TrimRight(suffix, ".0123456789s") - suffix = strings.TrimSuffix(suffix, " (") - newFailing = append(newFailing, suffix) - } - } - newFailingSorted := slices.Clone(newFailing) - slices.Sort(newFailingSorted) - - // Produce a sorted list of previously known failures. - oldFailing := strings.Split(strings.TrimSuffix(knownFailuresText, "\n"), "\n") - for i, s := range oldFailing { - oldFailing[i] = strings.TrimRight(s, "\r") - } - oldFailingSorted := slices.Clone(oldFailing) - slices.Sort(oldFailingSorted) - - // Check whether the two lists match. - if !slices.Equal(newFailingSorted, oldFailingSorted) { - var diff []string - before, after := oldFailingSorted, newFailingSorted - for len(before)|len(after) > 0 { - switch { - case len(before) == 0: - diff = append(diff, fmt.Sprintf("+ %s\n", after[0])) - after = after[1:] - case len(after) == 0: - diff = append(diff, fmt.Sprintf("- %s\n", before[0])) - before = before[1:] - case after[0] < before[0]: - diff = append(diff, fmt.Sprintf("+ %s\n", after[0])) - after = after[1:] - case before[0] < after[0]: - diff = append(diff, fmt.Sprintf("- %s\n", before[0])) - before = before[1:] - default: - before, after = before[1:], after[1:] - } - } - t.Errorf("known failures mismatch (-old +new):\n%s", strings.Join(diff, "")) - if *updateKnownFailures { - if err := os.WriteFile("failing.txt", []byte(strings.Join(newFailing, "\n")+"\n"), 0664); err != nil { - t.Errorf("os.WriteFile error: %v", err) - } - } - } -} diff --git a/v1/inject.go b/v1/inject.go index 13bbd1f..1a92455 100644 --- a/v1/inject.go +++ b/v1/inject.go @@ -29,7 +29,7 @@ func transformMarshalError(root any, err error) error { // Historically, errors returned from Marshal methods were wrapped // in a [MarshalerError]. This is directly performed by the v2 package // via the injected [internal.NewMarshalerError] constructor - // while operating under [ReportLegacyErrorValues]. + // while operating under [ReportErrorsWithLegacySemantics]. // Note that errors from a Marshal method were always wrapped, // even if wrapped for multiple layers. if err, ok := err.(*jsonv2.SemanticError); err != nil { @@ -57,7 +57,7 @@ func transformMarshalError(root any, err error) error { func transformUnmarshalError(root any, err error) error { // Historically, errors from Unmarshal methods were never wrapped and - // returned verbatim while operating under [ReportLegacyErrorValues]. + // returned verbatim while operating under [ReportErrorsWithLegacySemantics]. if err, ok := err.(*jsonv2.SemanticError); err != nil { if err.Err == internal.ErrNonNilReference { return &InvalidUnmarshalError{err.GoType} @@ -79,7 +79,7 @@ func transformUnmarshalError(root any, err error) error { // to use a '.'-delimited representation. This may be ambiguous, // but the prior representation was always ambiguous as well. // Users that care about precise positions should use v2 errors - // by disabling [ReportLegacyErrorValues]. + // by disabling [ReportErrorsWithLegacySemantics]. // // The introduction of a Err field is new to the v1-to-v2 migration // and allows us to preserve stronger error information diff --git a/v1/options.go b/v1/options.go index 5fcbf25..a514f8d 100644 --- a/v1/options.go +++ b/v1/options.go @@ -46,7 +46,7 @@ type Options = jsonopts.Options // - [OmitEmptyWithLegacyDefinition] // - [PreserveRawStrings] // - [RejectFloatOverflow] -// - [ReportLegacyErrorValues] +// - [ReportErrorsWithLegacySemantics] // - [StringifyWithLegacySemantics] // - [UnmarshalArrayFromAnyLength] // - [jsonv2.Deterministic] @@ -193,21 +193,6 @@ func FormatTimeWithLegacySemantics(v bool) Options { } } -// IgnoreStructErrors specifies that a Go struct with structural errors -// should not result in a runtime error when marshaling or unmarshaling. -// Such errors usually occur because of a malformed struct field tag -// as it pertains to JSON serialization. -// -// This affects either marshaling or unmarshaling. -// The v1 default is true. -func IgnoreStructErrors(v bool) Options { - if v { - return jsonflags.IgnoreStructErrors | 1 - } else { - return jsonflags.IgnoreStructErrors | 0 - } -} - // MatchCaseSensitiveDelimiter specifies that underscores and dashes are // not to be ignored when performing case-insensitive name matching which // occurs under [jsonv2.MatchCaseInsensitiveNames] or the `nocase` tag option. @@ -306,19 +291,55 @@ func RejectFloatOverflow(v bool) Options { } } -// ReportLegacyErrorValues specifies that Marshal and Unmarshal should -// return legacy error values such as [SyntaxError], [MarshalerError], -// [UnsupportedTypeError], [UnsupportedValueError], -// [InvalidUnmarshalError], or [UnmarshalTypeError] instead of the -// [jsonv2.SemanticError] or [jsontext.SyntacticError]. +// ReportErrorsWithLegacySemantics specifies that Marshal and Unmarshal +// should report errors with legacy semantics: +// +// - When marshaling or unmarshaling, the returned error values are +// usually of types such as [SyntaxError], [MarshalerError], +// [UnsupportedTypeError], [UnsupportedValueError], +// [InvalidUnmarshalError], or [UnmarshalTypeError]. +// In contrast, the v2 semantic is to always return errors as either +// [jsonv2.SemanticError] or [jsontext.SyntacticError]. +// +// - When marshaling, if a user-defined marshal method reports an error, +// it is always wrapped in a [MarshalerError], even if the error itself +// is already a [MarshalerError], which may lead to multiple redundant +// layers of wrapping. In contrast, the v2 semantic is to +// always wrap an error within [jsonv2.SemanticError] +// unless it is already a semantic error. +// +// - When unmarshaling, if a user-defined unmarshal method reports an error, +// it is never wrapped and reported verbatim. In contrast, the v2 semantic +// is to always wrap an error within [jsonv2.SemanticError] +// unless it is already a semantic error. +// +// - When marshaling or unmarshaling, if a Go struct contains type errors +// (e.g., conflicting names or malformed field tags), then such errors +// are ignored and the Go struct uses a best-effort representation. +// In contrast, the v2 semantic is to report a runtime error. +// +// - When unmarshaling, the syntactic structure of the JSON input +// is fully validated before performing the semantic unmarshaling +// of the JSON data into the Go value. Practically speaking, +// this means that JSON input with syntactic errors do not result +// in any mutations of the target Go value. In contrast, the v2 semantic +// is to perform a streaming decode and unmarshal of the JSON input +// into the target Go value, which means that the Go value may be +// partially mutated when a syntactic error is encountered. +// +// - When unmarshaling, a semantic error does not immediately terminate the +// unmarshal procedure, but rather evaluation continues. +// When unmarshal returns, only the first semantic error is reported. +// In contrast, the v2 semantic is to terminate unmarshal the moment +// an error is encountered. // // This affects either marshaling or unmarshaling. // The v1 default is true. -func ReportLegacyErrorValues(v bool) Options { +func ReportErrorsWithLegacySemantics(v bool) Options { if v { - return jsonflags.ReportLegacyErrorValues | 1 + return jsonflags.ReportErrorsWithLegacySemantics | 1 } else { - return jsonflags.ReportLegacyErrorValues | 0 + return jsonflags.ReportErrorsWithLegacySemantics | 0 } } diff --git a/v1/scanner_test.go b/v1/scanner_test.go index 1978ed5..8b31b23 100644 --- a/v1/scanner_test.go +++ b/v1/scanner_test.go @@ -195,7 +195,6 @@ func TestIndentErrors(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) slice := make([]uint8, 0) buf := bytes.NewBuffer(slice) if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { diff --git a/v1/stream.go b/v1/stream.go index 59a5ab1..5ac7b8f 100644 --- a/v1/stream.go +++ b/v1/stream.go @@ -63,13 +63,8 @@ func (dec *Decoder) Decode(v any) error { if dec.err != nil { return dec.err } - data, err := dec.dec.ReadValue() - if err != nil { - err = transformSyntacticError(err) - dec.err = err - return err - } - return jsonv2.Unmarshal(data, v, dec.opts) + dec.err = jsonv2.UnmarshalDecode(dec.dec, v, dec.opts) + return dec.err } // Buffered returns a reader of the data remaining in the Decoder's diff --git a/v1/stream_test.go b/v1/stream_test.go index 1fd938d..38bc0dc 100644 --- a/v1/stream_test.go +++ b/v1/stream_test.go @@ -194,7 +194,6 @@ func TestEncoderSetEscapeHTML(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) var buf strings.Builder enc := NewEncoder(&buf) if err := enc.Encode(tt.v); err != nil { @@ -285,7 +284,6 @@ func nlines(s string, n int) string { } func TestRawMessage(t *testing.T) { - skipKnownFailure(t) var data struct { X float64 Id RawMessage @@ -442,7 +440,6 @@ func TestDecodeInStream(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) dec := NewDecoder(strings.NewReader(tt.json)) for i, want := range tt.expTokens { var got any diff --git a/v1/tagkey_test.go b/v1/tagkey_test.go index b154ad5..7f59f0d 100644 --- a/v1/tagkey_test.go +++ b/v1/tagkey_test.go @@ -96,7 +96,6 @@ func TestStructTagObjectKey(t *testing.T) { } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { - skipKnownFailure(t) b, err := Marshal(tt.raw) if err != nil { t.Fatalf("%s: Marshal error: %v", tt.Where, err)