From 6d0bcc153978de303f273066de1cce8540edddf4 Mon Sep 17 00:00:00 2001 From: Mark Rosemaker <48681726+MarkRosemaker@users.noreply.github.com> Date: Sat, 28 Dec 2024 17:03:55 +0100 Subject: [PATCH] update github.com/go-json-experiment/json --- go.mod | 2 +- go.sum | 4 +- .../go-json-experiment/json/README.md | 47 ++- .../go-json-experiment/json/arshal.go | 88 +++-- .../go-json-experiment/json/arshal_any.go | 6 +- .../go-json-experiment/json/arshal_default.go | 301 +++++++++++++----- .../go-json-experiment/json/arshal_funcs.go | 13 + .../go-json-experiment/json/arshal_inlined.go | 15 +- .../go-json-experiment/json/arshal_methods.go | 245 +++++++++----- .../go-json-experiment/json/arshal_time.go | 8 +- .../github.com/go-json-experiment/json/doc.go | 6 +- .../go-json-experiment/json/errors.go | 23 +- .../go-json-experiment/json/fields.go | 186 +++++++---- .../json/internal/internal.go | 25 ++ .../json/internal/jsonflags/flags.go | 13 +- .../json/internal/jsonwire/encode.go | 51 ++- .../go-json-experiment/json/jsontext/value.go | 3 + vendor/modules.txt | 2 +- 18 files changed, 720 insertions(+), 318 deletions(-) diff --git a/go.mod b/go.mod index 14b181c..637e699 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/MarkRosemaker/jsonutil go 1.23.2 -require github.com/go-json-experiment/json v0.0.0-20241215161817-a8b28468c6ae +require github.com/go-json-experiment/json v0.0.0-20241228065829-3e670832f349 diff --git a/go.sum b/go.sum index 2955450..0806b0a 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/go-json-experiment/json v0.0.0-20241215161817-a8b28468c6ae h1:xN+U/k+5TS4oMol/3/sWnzRsK5u1GIgw/612Mk7HJXw= -github.com/go-json-experiment/json v0.0.0-20241215161817-a8b28468c6ae/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= +github.com/go-json-experiment/json v0.0.0-20241228065829-3e670832f349 h1:TcKFl0JuLr+dYE/9/SC75xz5QJBQONb2VzruJlO/5s4= +github.com/go-json-experiment/json v0.0.0-20241228065829-3e670832f349/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= diff --git a/vendor/github.com/go-json-experiment/json/README.md b/vendor/github.com/go-json-experiment/json/README.md index 243e2af..0bc0735 100644 --- a/vendor/github.com/go-json-experiment/json/README.md +++ b/vendor/github.com/go-json-experiment/json/README.md @@ -141,30 +141,29 @@ This table shows an overview of the changes: | v1 | v2 | Details | | -- | -- | ------- | -| JSON object members are unmarshaled into a Go struct using a **case-insensitive name match**. | JSON object members are unmarshaled into a Go struct using a **case-sensitive name match**. | [CaseSensitivity](/diff_test.go#:~:text=TestCaseSensitivity) | -| When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value is an empty Go value**, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. | When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value would encode as an empty JSON value**, which is defined as a JSON null, or an empty JSON string, object, or array. | [OmitEmptyOption](/diff_test.go#:~:text=TestOmitEmptyOption) | -| The `string` option **does affect** Go bools. | The `string` option **does not affect** Go bools. | [StringOption](/diff_test.go#:~:text=TestStringOption) | -| The `string` option **does not recursively affect** sub-values of the Go field value. | The `string` option **does recursively affect** sub-values of the Go field value. | [StringOption](/diff_test.go#:~:text=TestStringOption) | -| The `string` option **sometimes accepts** a JSON null escaped within a JSON string. | The `string` option **never accepts** a JSON null escaped within a JSON string. | [StringOption](/diff_test.go#:~:text=TestStringOption) | -| A nil Go slice is marshaled as a **JSON null**. | A nil Go slice is marshaled as an **empty JSON array**. | [NilSlicesAndMaps](/diff_test.go#:~:text=TestNilSlicesAndMaps) | -| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/diff_test.go#:~:text=TestNilSlicesAndMaps) | -| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/diff_test.go#:~:text=Arrays) | -| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/diff_test.go#:~:text=TestByteArrays) | -| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/diff_test.go#:~:text=TestPointerReceiver) | -| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/diff_test.go#:~:text=TestMapDeterminism) | -| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/diff_test.go#:~:text=TestEscapeHTML) | -| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/diff_test.go#:~:text=TestInvalidUTF8) | -| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/diff_test.go#:~:text=TestInvalidUTF8) | -| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/diff_test.go#:~:text=TestDuplicateNames) | -| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/diff_test.go#:~:text=TestDuplicateNames) | -| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/diff_test.go#:~:text=TestMergeNull) | -| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/diff_test.go#:~:text=TestMergeComposite) | -| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/diff_test.go#:~:text=TestTimeDurations) | -| Unmarshaling a JSON number into a Go float beyond its representation **results in an error**. | Unmarshaling a JSON number into a Go float beyond its representation **uses the closest representable value (e.g., ±`math.MaxFloat`)**. | [MaxFloats](/diff_test.go#:~:text=TestMaxFloats) | -| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/diff_test.go#:~:text=TestEmptyStructs) | -| A Go struct that embeds an unexported struct type **can sometimes be serialized**. | A Go struct that embeds an unexported struct type **cannot be serialized**. | [EmbedUnexported](/diff_test.go#:~:text=TestEmbedUnexported) | - -See [diff_test.go](/diff_test.go) for details about every change. +| JSON object members are unmarshaled into a Go struct using a **case-insensitive name match**. | JSON object members are unmarshaled into a Go struct using a **case-sensitive name match**. | [CaseSensitivity](/v1/diff_test.go#:~:text=TestCaseSensitivity) | +| When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value is an empty Go value**, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. | When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value would encode as an empty JSON value**, which is defined as a JSON null, or an empty JSON string, object, or array. | [OmitEmptyOption](/v1/diff_test.go#:~:text=TestOmitEmptyOption) | +| The `string` option **does affect** Go strings and bools. | The `string` option **does not affect** Go strings or bools. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) | +| The `string` option **does not recursively affect** sub-values of the Go field value. | The `string` option **does recursively affect** sub-values of the Go field value. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) | +| The `string` option **sometimes accepts** a JSON null escaped within a JSON string. | The `string` option **never accepts** a JSON null escaped within a JSON string. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) | +| A nil Go slice is marshaled as a **JSON null**. | A nil Go slice is marshaled as an **empty JSON array**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) | +| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) | +| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/v1/diff_test.go#:~:text=Arrays) | +| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/v1/diff_test.go#:~:text=TestByteArrays) | +| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/v1/diff_test.go#:~:text=TestPointerReceiver) | +| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/v1/diff_test.go#:~:text=TestMapDeterminism) | +| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/v1/diff_test.go#:~:text=TestEscapeHTML) | +| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) | +| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) | +| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) | +| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) | +| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/v1/diff_test.go#:~:text=TestMergeNull) | +| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/v1/diff_test.go#:~:text=TestMergeComposite) | +| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/v1/diff_test.go#:~:text=TestTimeDurations) | +| Unmarshaling a JSON number into a Go float beyond its representation **results in an error**. | Unmarshaling a JSON number into a Go float beyond its representation **uses the closest representable value (e.g., ±`math.MaxFloat`)**. | [MaxFloats](/v1/diff_test.go#:~:text=TestMaxFloats) | +| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/v1/diff_test.go#:~:text=TestEmptyStructs) | + +See [diff_test.go](/v1/diff_test.go) for details about every change. ## Performance diff --git a/vendor/github.com/go-json-experiment/json/arshal.go b/vendor/github.com/go-json-experiment/json/arshal.go index 8f9a1a0..8b590a4 100644 --- a/vendor/github.com/go-json-experiment/json/arshal.go +++ b/vendor/github.com/go-json-experiment/json/arshal.go @@ -6,7 +6,6 @@ package json import ( "bytes" - "errors" "io" "reflect" "slices" @@ -90,11 +89,12 @@ func putStructOptions(o *jsonopts.Struct) { // where each byte is recursively JSON-encoded as each JSON array element. // // - A Go integer is encoded as a JSON number without fractions or exponents. -// If [StringifyNumbers] is specified, then the JSON number is -// encoded within a JSON string. It does not support any custom format flags. +// If [StringifyNumbers] is specified or encoding a JSON object name, +// then the JSON number is encoded within a JSON string. +// It does not support any custom format flags. // // - A Go float is encoded as a JSON number. -// If [StringifyNumbers] is specified, +// If [StringifyNumbers] is specified or encoding a JSON object name, // then the JSON number is encoded within a JSON string. // If the format is "nonfinite", then NaN, +Inf, and -Inf are encoded as // the JSON strings "NaN", "Infinity", and "-Infinity", respectively. @@ -103,9 +103,7 @@ func putStructOptions(o *jsonopts.Struct) { // - A Go map is encoded as a JSON object, where each Go map key and value // is recursively encoded as a name and value pair in the JSON object. // The Go map key must encode as a JSON string, otherwise this results -// in a [SemanticError]. When encoding keys, [StringifyNumbers] -// is automatically applied so that numeric keys encode as JSON strings. -// The Go map is traversed in a non-deterministic order. +// in a [SemanticError]. The Go map is traversed in a non-deterministic order. // For deterministic encoding, consider using [jsontext.Value.Canonicalize]. // If the format is "emitnull", then a nil map is encoded as a JSON null. // If the format is "emitempty", then a nil map is encoded as an empty JSON object, @@ -166,6 +164,9 @@ 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) { + return nil, internal.TransformMarshalError(in, err) + } return bytes.Clone(xe.Buf), err } @@ -178,7 +179,11 @@ func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) { defer export.PutStreamingEncoder(enc) xe := export.Encoder(enc) xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1) - return marshalEncode(enc, in, &xe.Struct) + err = marshalEncode(enc, in, &xe.Struct) + if err != nil && xe.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.TransformMarshalError(in, err) + } + return err } // MarshalEncode serializes a Go value into an [jsontext.Encoder] according to @@ -192,7 +197,11 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) { mo.Join(opts...) xe := export.Encoder(out) mo.CopyCoderOptions(&xe.Struct) - return marshalEncode(out, in, mo) + err = marshalEncode(out, in, mo) + if err != nil && xe.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.TransformMarshalError(in, err) + } + return err } func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err error) { @@ -202,12 +211,13 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro } // Shallow copy non-pointer values to obtain an addressable value. // It is beneficial to performance to always pass pointers to avoid this. - if v.Kind() != reflect.Pointer { + forceAddr := v.Kind() != reflect.Pointer + if forceAddr { v2 := reflect.New(v.Type()) v2.Elem().Set(v) v = v2 } - va := addressableValue{v.Elem()} // dereferenced pointer is always addressable + va := addressableValue{v.Elem(), forceAddr} // dereferenced pointer is always addressable t := va.Type() // Lookup and call the marshal function for this type. @@ -293,16 +303,16 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro // otherwise it fails with a [SemanticError]. // // - A Go integer is decoded from a JSON number. -// It may also be decoded from a JSON string containing a JSON number -// if [StringifyNumbers] is specified. +// It must be decoded from a JSON string containing a JSON number +// if [StringifyNumbers] is specified or decoding a JSON object name. // It fails with a [SemanticError] if the JSON number // has a fractional or exponent component. // It also fails if it overflows the representation of the Go integer type. // It does not support any custom format flags. // // - A Go float is decoded from a JSON number. -// It may also be decoded from a JSON string containing a JSON number -// if [StringifyNumbers] is specified. +// It must be decoded from a JSON string containing a JSON number +// if [StringifyNumbers] is specified or decoding a JSON object name. // The JSON number is parsed as the closest representable Go float value. // If the format is "nonfinite", then the JSON strings // "NaN", "Infinity", and "-Infinity" are decoded as NaN, +Inf, and -Inf. @@ -310,9 +320,7 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro // // - A Go map is decoded from a JSON object, // where each JSON object name and value pair is recursively decoded -// as the Go map key and value. When decoding keys, -// [StringifyNumbers] is automatically applied so that -// numeric keys can decode from JSON strings. Maps are not cleared. +// as the Go map key and value. Maps are not cleared. // If the Go map is nil, then a new map is allocated to decode into. // If the decoded key matches an existing Go map entry, the entry value // is reused by decoding the JSON object value into it. @@ -384,7 +392,11 @@ func Unmarshal(in []byte, out any, opts ...Options) (err error) { dec := export.GetBufferedDecoder(in, opts...) defer export.PutBufferedDecoder(dec) xd := export.Decoder(dec) - return unmarshalFull(dec, out, &xd.Struct) + err = unmarshalFull(dec, out, &xd.Struct) + if err != nil && xd.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.TransformUnmarshalError(out, err) + } + return err } // UnmarshalRead deserializes a Go value from an [io.Reader] according to the @@ -397,7 +409,11 @@ func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) { dec := export.GetStreamingDecoder(in, opts...) defer export.PutStreamingDecoder(dec) xd := export.Decoder(dec) - return unmarshalFull(dec, out, &xd.Struct) + err = unmarshalFull(dec, out, &xd.Struct) + if err != nil && xd.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.TransformUnmarshalError(out, err) + } + return err } func unmarshalFull(in *jsontext.Decoder, out any, uo *jsonopts.Struct) error { @@ -425,24 +441,19 @@ func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error) uo.Join(opts...) xd := export.Decoder(in) uo.CopyCoderOptions(&xd.Struct) - return unmarshalDecode(in, out, uo) + err = unmarshalDecode(in, out, uo) + if err != nil && uo.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.TransformUnmarshalError(out, err) + } + return err } -var errNonNilReference = errors.New("value must be passed as a non-nil pointer reference") - func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err error) { v := reflect.ValueOf(out) - if !v.IsValid() || v.Kind() != reflect.Pointer || v.IsNil() { - var t reflect.Type - if v.IsValid() { - t = v.Type() - if t.Kind() == reflect.Pointer { - t = t.Elem() - } - } - return &SemanticError{action: "unmarshal", GoType: t, Err: errNonNilReference} + if v.Kind() != reflect.Pointer || v.IsNil() { + return &SemanticError{action: "unmarshal", GoType: reflect.TypeOf(out), Err: internal.ErrNonNilReference} } - va := addressableValue{v.Elem()} // dereferenced pointer is always addressable + va := addressableValue{v.Elem(), false} // dereferenced pointer is always addressable t := va.Type() // Lookup and call the unmarshal function for this type. @@ -466,11 +477,18 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err er // There is no compile magic that enforces this property, // but rather the need to construct this type makes it easier to examine each // construction site to ensure that this property is upheld. -type addressableValue struct{ reflect.Value } +type addressableValue struct { + reflect.Value + + // forcedAddr reports whether this value is addressable + // only through the use of [newAddressableValue]. + // This is only used for [jsonflags.CallMethodsWithLegacySemantics]. + forcedAddr bool +} // newAddressableValue constructs a new addressable value of type t. func newAddressableValue(t reflect.Type) addressableValue { - return addressableValue{reflect.New(t).Elem()} + return addressableValue{reflect.New(t).Elem(), true} } // All marshal and unmarshal behavior is implemented using these signatures. diff --git a/vendor/github.com/go-json-experiment/json/arshal_any.go b/vendor/github.com/go-json-experiment/json/arshal_any.go index 4355089..b4b9987 100644 --- a/vendor/github.com/go-json-experiment/json/arshal_any.go +++ b/vendor/github.com/go-json-experiment/json/arshal_any.go @@ -8,6 +8,7 @@ import ( "reflect" "strconv" + "github.com/go-json-experiment/json/internal" "github.com/go-json-experiment/json/internal/jsonflags" "github.com/go-json-experiment/json/internal/jsonopts" "github.com/go-json-experiment/json/internal/jsonwire" @@ -71,9 +72,12 @@ func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error) } return makeString(xd.StringCache, val), nil case '0': + if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) { + return internal.RawNumberOf(val), nil + } fv, ok := jsonwire.ParseFloat(val, 64) if !ok && uo.Flags.Get(jsonflags.RejectFloatOverflow) { - return nil, newUnmarshalErrorAfter(dec, float64Type, strconv.ErrRange) + return nil, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange) } return fv, nil default: diff --git a/vendor/github.com/go-json-experiment/json/arshal_default.go b/vendor/github.com/go-json-experiment/json/arshal_default.go index 1929f5b..6c0e4a6 100644 --- a/vendor/github.com/go-json-experiment/json/arshal_default.go +++ b/vendor/github.com/go-json-experiment/json/arshal_default.go @@ -6,6 +6,7 @@ package json import ( "bytes" + "encoding" "encoding/base32" "encoding/base64" "encoding/hex" @@ -17,6 +18,7 @@ import ( "strconv" "sync" + "github.com/go-json-experiment/json/internal" "github.com/go-json-experiment/json/internal/jsonflags" "github.com/go-json-experiment/json/internal/jsonopts" "github.com/go-json-experiment/json/internal/jsonwire" @@ -51,14 +53,12 @@ type typedPointer struct { len int // remember slice length to avoid false positives } -var errCycle = errors.New("encountered a cycle") - // visitPointer visits pointer p of type t, reporting an error if seen before. // If successfully visited, then the caller must eventually call leave. func visitPointer(m *seenPointers, v reflect.Value) error { p := typedPointer{v.Type(), v.UnsafePointer(), sliceLen(v)} if _, ok := (*m)[p]; ok { - return errCycle + return internal.ErrCycle } if *m == nil { *m = make(seenPointers) @@ -100,13 +100,13 @@ func makeDefaultArshaler(t reflect.Type) *arshaler { return makeStructArshaler(t) case reflect.Slice: fncs := makeSliceArshaler(t) - if t.AssignableTo(bytesType) { + if t.Elem().Kind() == reflect.Uint8 { return makeBytesArshaler(t, fncs) } return fncs case reflect.Array: fncs := makeArrayArshaler(t) - if reflect.SliceOf(t.Elem()).AssignableTo(bytesType) { + if t.Elem().Kind() == reflect.Uint8 { return makeBytesArshaler(t, fncs) } return fncs @@ -158,7 +158,9 @@ func makeBoolArshaler(t reflect.Type) *arshaler { k := tok.Kind() switch k { case 'n': - va.SetBool(false) + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetBool(false) + } return nil case 't', 'f': if !uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { @@ -173,7 +175,13 @@ func makeBoolArshaler(t reflect.Type) *arshaler { case "false": va.SetBool(false) default: - return newUnmarshalErrorAfter(dec, t, fmt.Errorf("cannot parse %q as bool", tok.String())) + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && tok.String() == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetBool(false) + } + return nil + } + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) } return nil } @@ -208,10 +216,13 @@ func makeStringArshaler(t reflect.Type) *arshaler { } if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { - b, err := jsontext.AppendQuote(nil, s) // only fails for invalid UTF-8 - q, _ := jsontext.AppendQuote(nil, b) // cannot fail since b is valid UTF-8 - if err != nil && !xe.Flags.Get(jsonflags.AllowInvalidUTF8) { - return newMarshalErrorBefore(enc, t, err) + b, err := jsonwire.AppendQuote(nil, s, &mo.Flags) + if err != nil { + return newMarshalErrorBefore(enc, t, &jsontext.SyntacticError{Err: err}) + } + q, err := jsontext.AppendQuote(nil, b) + if err != nil { + panic("BUG: second AppendQuote should never fail: " + err.Error()) } return enc.WriteValue(q) } @@ -230,7 +241,9 @@ func makeStringArshaler(t reflect.Type) *arshaler { k := val.Kind() switch k { case 'n': - va.SetString("") + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetString("") + } return nil case '"': val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) @@ -239,6 +252,12 @@ func makeStringArshaler(t reflect.Type) *arshaler { if err != nil { return newUnmarshalErrorAfter(dec, t, err) } + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetString("") + } + return nil + } } if xd.StringCache == nil { xd.StringCache = new(stringCache) @@ -276,9 +295,19 @@ var ( ) func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { - // NOTE: This handles both []byte and [N]byte. + // NOTE: This handles both []~byte and [N]~byte. + // The v2 default is to treat a []namedByte as equivalent to []T + // since being able to convert []namedByte to []byte relies on + // dubious Go reflection behavior (see https://go.dev/issue/24746). + // For v1 emulation, we use jsonflags.FormatBytesWithLegacySemantics + // to forcibly treat []namedByte as a []byte. marshalArray := fncs.marshal + isNamedByte := t.Elem().PkgPath() != "" + hasMarshaler := implementsAny(t.Elem(), allMarshalerTypes...) fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + if !mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && isNamedByte { + return marshalArray(enc, va, mo) // treat as []T or [N]T + } xe := export.Encoder(enc) encode, encodedLen := encodeBase64, encodedLenBase64 if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { @@ -299,7 +328,8 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { default: return newInvalidFormatError(enc, t, mo.Format) } - } else if mo.Flags.Get(jsonflags.FormatByteArrayAsArray) && va.Kind() == reflect.Array { + } else if mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && + (va.Kind() == reflect.Array || hasMarshaler) { return marshalArray(enc, va, mo) } if mo.Flags.Get(jsonflags.FormatNilSliceAsNull) && va.Kind() == reflect.Slice && va.IsNil() { @@ -321,6 +351,9 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { } unmarshalArray := fncs.unmarshal fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + if !uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && isNamedByte { + return unmarshalArray(dec, va, uo) // treat as []T or [N]T + } xd := export.Decoder(dec) decode, decodedLen, encodedLen := decodeBase64, decodedLenBase64, encodedLenBase64 if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { @@ -341,7 +374,8 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { default: return newInvalidFormatError(dec, t, uo.Format) } - } else if uo.Flags.Get(jsonflags.FormatByteArrayAsArray) && va.Kind() == reflect.Array { + } else if uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && + (va.Kind() == reflect.Array || dec.PeekKind() == '[') { return unmarshalArray(dec, va, uo) } var flags jsonwire.ValueFlags @@ -352,7 +386,9 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { k := val.Kind() switch k { case 'n': - va.SetZero() + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) || va.Kind() != reflect.Array { + va.SetZero() + } return nil case '"': val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) @@ -369,7 +405,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { b := va.Bytes() if va.Kind() == reflect.Array { if n != len(b) { - err := fmt.Errorf("decoded base64 length of %d mismatches array length of %d", n, len(b)) + err := fmt.Errorf("decoded length of %d mismatches array length of %d", n, len(b)) return newUnmarshalErrorAfter(dec, t, err) } } else { @@ -385,7 +421,8 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { // specifies that non-alphabet characters must be rejected. // Unfortunately, the "base32" and "base64" packages allow // '\r' and '\n' characters by default. - err = errors.New("illegal data at input byte " + strconv.Itoa(bytes.IndexAny(val, "\r\n"))) + i := bytes.IndexAny(val, "\r\n") + err = fmt.Errorf("illegal character %s at offset %d", jsonwire.QuoteRune(val[i:]), i) } if err != nil { return newUnmarshalErrorAfter(dec, t, err) @@ -395,7 +432,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { } return nil } - return newUnmarshalErrorAfter(dec, t, err) + return newUnmarshalErrorAfter(dec, t, nil) } return fncs } @@ -419,7 +456,7 @@ func makeIntArshaler(t reflect.Type) *arshaler { return nil } - k := stringOrNumberKind(mo.Flags.Get(jsonflags.StringifyNumbers)) + k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) return xe.AppendRaw(k, true, func(b []byte) ([]byte, error) { return strconv.AppendInt(b, va.Int(), 10), nil }) @@ -429,6 +466,7 @@ func makeIntArshaler(t reflect.Type) *arshaler { if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { return newInvalidFormatError(dec, t, uo.Format) } + stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) var flags jsonwire.ValueFlags val, err := xd.ReadValue(&flags) if err != nil { @@ -437,16 +475,24 @@ func makeIntArshaler(t reflect.Type) *arshaler { k := val.Kind() switch k { case 'n': - va.SetInt(0) + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetInt(0) + } return nil case '"': - if !uo.Flags.Get(jsonflags.StringifyNumbers) { + if !stringify { break } val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetInt(0) + } + return nil + } fallthrough case '0': - if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' { + if stringify && k == '0' { break } var negOffset int @@ -459,14 +505,12 @@ func makeIntArshaler(t reflect.Type) *arshaler { overflow := (neg && n > maxInt) || (!neg && n > maxInt-1) if !ok { if n != math.MaxUint64 { - err := fmt.Errorf("cannot parse %q as signed integer: %w", val, strconv.ErrSyntax) - return newUnmarshalErrorAfter(dec, t, err) + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) } overflow = true } if overflow { - err := fmt.Errorf("cannot parse %q as signed integer: %w", val, strconv.ErrRange) - return newUnmarshalErrorAfter(dec, t, err) + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange) } if neg { va.SetInt(int64(-n)) @@ -499,7 +543,7 @@ func makeUintArshaler(t reflect.Type) *arshaler { return nil } - k := stringOrNumberKind(mo.Flags.Get(jsonflags.StringifyNumbers)) + k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) return xe.AppendRaw(k, true, func(b []byte) ([]byte, error) { return strconv.AppendUint(b, va.Uint(), 10), nil }) @@ -509,6 +553,7 @@ func makeUintArshaler(t reflect.Type) *arshaler { if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { return newInvalidFormatError(dec, t, uo.Format) } + stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) var flags jsonwire.ValueFlags val, err := xd.ReadValue(&flags) if err != nil { @@ -517,16 +562,24 @@ func makeUintArshaler(t reflect.Type) *arshaler { k := val.Kind() switch k { case 'n': - va.SetUint(0) + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetUint(0) + } return nil case '"': - if !uo.Flags.Get(jsonflags.StringifyNumbers) { + if !stringify { break } val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetUint(0) + } + return nil + } fallthrough case '0': - if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' { + if stringify && k == '0' { break } n, ok := jsonwire.ParseUint(val) @@ -534,14 +587,12 @@ func makeUintArshaler(t reflect.Type) *arshaler { overflow := n > maxUint-1 if !ok { if n != math.MaxUint64 { - err := fmt.Errorf("cannot parse %q as unsigned integer: %w", val, strconv.ErrSyntax) - return newUnmarshalErrorAfter(dec, t, err) + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) } overflow = true } if overflow { - err := fmt.Errorf("cannot parse %q as unsigned integer: %w", val, strconv.ErrRange) - return newUnmarshalErrorAfter(dec, t, err) + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange) } va.SetUint(n) return nil @@ -568,7 +619,7 @@ func makeFloatArshaler(t reflect.Type) *arshaler { fv := va.Float() if math.IsNaN(fv) || math.IsInf(fv, 0) { if !allowNonFinite { - err := fmt.Errorf("invalid value: %v", fv) + err := fmt.Errorf("unsupported value: %v", fv) return newMarshalErrorBefore(enc, t, err) } return enc.WriteToken(jsontext.Float(fv)) @@ -584,7 +635,7 @@ func makeFloatArshaler(t reflect.Type) *arshaler { return nil } - k := stringOrNumberKind(mo.Flags.Get(jsonflags.StringifyNumbers)) + k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) return xe.AppendRaw(k, true, func(b []byte) ([]byte, error) { return jsonwire.AppendFloat(b, va.Float(), bits), nil }) @@ -599,6 +650,7 @@ func makeFloatArshaler(t reflect.Type) *arshaler { return newInvalidFormatError(dec, t, uo.Format) } } + stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) var flags jsonwire.ValueFlags val, err := xd.ReadValue(&flags) if err != nil { @@ -607,7 +659,9 @@ func makeFloatArshaler(t reflect.Type) *arshaler { k := val.Kind() switch k { case 'n': - va.SetFloat(0) + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetFloat(0) + } return nil case '"': val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) @@ -624,21 +678,26 @@ func makeFloatArshaler(t reflect.Type) *arshaler { return nil } } - if !uo.Flags.Get(jsonflags.StringifyNumbers) { + if !stringify { break } + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetFloat(0) + } + return nil + } if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil { - err := fmt.Errorf("cannot parse %q as JSON number: %w", val, strconv.ErrSyntax) - return newUnmarshalErrorAfter(dec, t, err) + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) } fallthrough case '0': - if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' { + if stringify && k == '0' { break } fv, ok := jsonwire.ParseFloat(val, bits) if !ok && uo.Flags.Get(jsonflags.RejectFloatOverflow) { - return newUnmarshalErrorAfter(dec, t, strconv.ErrRange) + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange) } va.SetFloat(fv) return nil @@ -666,6 +725,8 @@ func makeMapArshaler(t reflect.Type) *arshaler { keyFncs = lookupArshaler(t.Key()) valFncs = lookupArshaler(t.Elem()) } + nillableLegacyKey := t.Key().Kind() == reflect.Pointer && + implementsAny(t.Key(), textMarshalerType, textAppenderType) fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { // Check for cycles. xe := export.Encoder(enc) @@ -735,15 +796,18 @@ func makeMapArshaler(t reflect.Type) *arshaler { case !mo.Flags.Get(jsonflags.Deterministic) || n <= 1: for iter := va.Value.MapRange(); iter.Next(); { k.SetIterKey(iter) - flagsOriginal := mo.Flags - mo.Flags.Set(jsonflags.StringifyNumbers | 1) // stringify for numeric keys err := marshalKey(enc, k, mo) - mo.Flags = flagsOriginal if err != nil { - if serr, ok := err.(*jsontext.SyntacticError); ok && serr.Err == jsontext.ErrNonStringName { - err = newMarshalErrorBefore(enc, k.Type(), err) + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + errors.Is(err, jsontext.ErrNonStringName) && nillableLegacyKey && k.IsNil() { + err = enc.WriteToken(jsontext.String("")) + } + if err != nil { + if serr, ok := err.(*jsontext.SyntacticError); ok && serr.Err == jsontext.ErrNonStringName { + err = newMarshalErrorBefore(enc, k.Type(), err) + } + return err } - return err } v.SetIterValue(iter) if err := marshalVal(enc, v, mo); err != nil { @@ -780,19 +844,22 @@ func makeMapArshaler(t reflect.Type) *arshaler { vals := reflect.MakeSlice(reflect.SliceOf(t.Elem()), n, n) for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ { // Marshal the member name. - k := addressableValue{keys.Index(i)} // indexed slice element is always addressable + k := addressableValue{keys.Index(i), true} // indexed slice element is always addressable k.SetIterKey(iter) - v := addressableValue{vals.Index(i)} // indexed slice element is always addressable + v := addressableValue{vals.Index(i), true} // indexed slice element is always addressable v.SetIterValue(iter) - flagsOriginal := mo.Flags - mo.Flags.Set(jsonflags.StringifyNumbers | 1) // stringify for numeric keys err := marshalKey(enc, k, mo) - mo.Flags = flagsOriginal if err != nil { - if serr, ok := err.(*jsontext.SyntacticError); ok && serr.Err == jsontext.ErrNonStringName { - err = newMarshalErrorBefore(enc, k.Type(), err) + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + errors.Is(err, jsontext.ErrNonStringName) && nillableLegacyKey && k.IsNil() { + err = enc.WriteToken(jsontext.String("")) + } + if err != nil { + if serr, ok := err.(*jsontext.SyntacticError); ok && serr.Err == jsontext.ErrNonStringName { + err = newMarshalErrorBefore(enc, k.Type(), err) + } + return err } - return err } name := xe.UnwriteOnlyObjectMemberName() members[i] = member{name, k, v} @@ -894,7 +961,11 @@ func makeMapArshaler(t reflect.Type) *arshaler { name := xd.PreviousTokenOrValue() return newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(name)) } - v.Set(v2) + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + v.Set(v2) + } else { + v.SetZero() + } } else { v.SetZero() } @@ -938,6 +1009,8 @@ func mapKeyWithUniqueRepresentation(k reflect.Kind, allowInvalidUTF8 bool) bool } } +var errNilField = errors.New("cannot set embedded pointer to unexported struct type") + func makeStructArshaler(t reflect.Type) *arshaler { // NOTE: The logic below disables namespaces for tracking duplicate names // and does the tracking locally with an efficient bit-set based on which @@ -958,7 +1031,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { return newInvalidFormatError(enc, t, mo.Format) } once.Do(init) - if errInit != nil { + if errInit != nil && !mo.Flags.Get(jsonflags.IgnoreStructErrors) { return newMarshalErrorBefore(enc, errInit.GoType, errInit.Err) } if err := enc.WriteToken(jsontext.ObjectStart); err != nil { @@ -969,7 +1042,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { xe.Tokens.Last.DisableNamespace() // we manually ensure unique names below for i := range fields.flattened { f := &fields.flattened[i] - v := addressableValue{va.Field(f.index[0])} // addressable if struct value is addressable + v := addressableValue{va.Field(f.index[0]), va.forcedAddr} // addressable if struct value is addressable if len(f.index) > 1 { v = v.fieldByIndex(f.index[1:], false) if !v.IsValid() { @@ -1030,7 +1103,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { // Append the token to the output and to the state machine. n0 := len(b) // offset before calling AppendQuote - if !xe.Flags.Get(jsonflags.EscapeForHTML | jsonflags.EscapeForJS) { + if !xe.Flags.Get(jsonflags.EscapeForHTML | jsonflags.EscapeForJS | jsonflags.EscapeInvalidUTF8) { b = append(b, f.quotedName...) } else { b, _ = jsonwire.AppendQuote(b, f.name, &xe.Flags) @@ -1126,11 +1199,13 @@ func makeStructArshaler(t reflect.Type) *arshaler { k := tok.Kind() switch k { case 'n': - va.SetZero() + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetZero() + } return nil case '{': once.Do(init) - if errInit != nil { + if errInit != nil && !uo.Flags.Get(jsonflags.IgnoreStructErrors) { return newUnmarshalErrorAfter(dec, errInit.GoType, errInit.Err) } var seenIdxs uintSet @@ -1196,9 +1271,12 @@ func makeStructArshaler(t reflect.Type) *arshaler { uo.FormatDepth = xd.Tokens.Depth() uo.Format = f.format } - v := addressableValue{va.Field(f.index[0])} // addressable if struct value is addressable + v := addressableValue{va.Field(f.index[0]), va.forcedAddr} // addressable if struct value is addressable if len(f.index) > 1 { v = v.fieldByIndex(f.index[1:], true) + if !v.IsValid() { + return newUnmarshalErrorBefore(dec, t, errNilField) + } } err = unmarshal(dec, v, uo) uo.Flags = flagsOriginal @@ -1223,7 +1301,7 @@ func (va addressableValue) fieldByIndex(index []int, mayAlloc bool) addressableV if !va.IsValid() { return va } - va = addressableValue{va.Field(i)} // addressable if struct value is addressable + va = addressableValue{va.Field(i), va.forcedAddr} // addressable if struct value is addressable } return va } @@ -1231,12 +1309,12 @@ func (va addressableValue) fieldByIndex(index []int, mayAlloc bool) addressableV func (va addressableValue) indirect(mayAlloc bool) addressableValue { if va.Kind() == reflect.Pointer { if va.IsNil() { - if !mayAlloc { + if !mayAlloc || !va.CanSet() { return addressableValue{} } va.Set(reflect.New(va.Type().Elem())) } - va = addressableValue{va.Elem()} // dereferenced pointer is always addressable + va = addressableValue{va.Elem(), false} // dereferenced pointer is always addressable } return va } @@ -1339,7 +1417,7 @@ func makeSliceArshaler(t reflect.Type) *arshaler { marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t.Elem()) } for i := range n { - v := addressableValue{va.Index(i)} // indexed slice element is always addressable + v := addressableValue{va.Index(i), false} // indexed slice element is always addressable if err := marshal(enc, v, mo); err != nil { return err } @@ -1389,9 +1467,9 @@ func makeSliceArshaler(t reflect.Type) *arshaler { va.SetLen(cap) mustZero = false // reflect.Value.Grow ensures new capacity is zero-initialized } - v := addressableValue{va.Index(i)} // indexed slice element is always addressable + v := addressableValue{va.Index(i), false} // indexed slice element is always addressable i++ - if mustZero { + if mustZero && !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { v.SetZero() } if err := unmarshal(dec, v, uo); err != nil { @@ -1441,7 +1519,7 @@ func makeArrayArshaler(t reflect.Type) *arshaler { marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t.Elem()) } for i := range n { - v := addressableValue{va.Index(i)} // indexed array element is addressable if array is addressable + v := addressableValue{va.Index(i), va.forcedAddr} // indexed array element is addressable if array is addressable if err := marshal(enc, v, mo); err != nil { return err } @@ -1463,7 +1541,9 @@ func makeArrayArshaler(t reflect.Type) *arshaler { k := tok.Kind() switch k { case 'n': - va.SetZero() + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetZero() + } return nil case '[': once.Do(init) @@ -1480,8 +1560,10 @@ func makeArrayArshaler(t reflect.Type) *arshaler { err = errArrayOverflow continue } - v := addressableValue{va.Index(i)} // indexed array element is addressable if array is addressable - v.SetZero() + v := addressableValue{va.Index(i), va.forcedAddr} // indexed array element is addressable if array is addressable + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + v.SetZero() + } if err := unmarshal(dec, v, uo); err != nil { return err } @@ -1532,7 +1614,7 @@ func makePointerArshaler(t reflect.Type) *arshaler { if mo.Marshalers != nil { marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t.Elem()) } - v := addressableValue{va.Elem()} // dereferenced pointer is always addressable + v := addressableValue{va.Elem(), false} // dereferenced pointer is always addressable return marshal(enc, v, mo) } fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { @@ -1552,8 +1634,23 @@ func makePointerArshaler(t reflect.Type) *arshaler { if va.IsNil() { va.Set(reflect.New(t.Elem())) } - v := addressableValue{va.Elem()} // dereferenced pointer is always addressable - return unmarshal(dec, v, uo) + v := addressableValue{va.Elem(), false} // dereferenced pointer is always addressable + if err := unmarshal(dec, v, uo); err != nil { + return err + } + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && + (uo.Flags.Get(jsonflags.StringifyNumbers) || uo.Flags.Get(jsonflags.StringifyBoolsAndStrings)) { + // A JSON null quoted within a JSON string should take effect + // within the pointer value, rather than the indirect value. + // + // TODO: This does not correctly handle escaped nulls + // (e.g., "\u006e\u0075\u006c\u006c"), but is good enough + // for such an esoteric use case of the `string` option. + if string(export.Decoder(dec).PreviousTokenOrValue()) == `"null"` { + va.SetZero() + } + } + return nil } return &fncs } @@ -1566,6 +1663,13 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { // store them back into the interface afterwards. var fncs arshaler + var whichMarshaler reflect.Type + for _, iface := range allMarshalerTypes { + if t.Implements(iface) { + whichMarshaler = t + break + } + } fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { xe := export.Encoder(enc) if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { @@ -1573,6 +1677,24 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { } if va.IsNil() { return enc.WriteToken(jsontext.Null) + } else if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && whichMarshaler != nil { + // The marshaler for a pointer never calls the method on a nil receiver. + // Wrap the nil pointer within a struct type so that marshal + // instead appears on a value receiver and may be called. + if va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil() { + v2 := newAddressableValue(whichMarshaler) + switch whichMarshaler { + case jsonMarshalerV2Type: + v2.Set(reflect.ValueOf(struct{ MarshalerV2 }{va.Elem().Interface().(MarshalerV2)})) + case jsonMarshalerV1Type: + v2.Set(reflect.ValueOf(struct{ MarshalerV1 }{va.Elem().Interface().(MarshalerV1)})) + case textAppenderType: + v2.Set(reflect.ValueOf(struct{ encodingTextAppender }{va.Elem().Interface().(encodingTextAppender)})) + case textMarshalerType: + v2.Set(reflect.ValueOf(struct{ encoding.TextMarshaler }{va.Elem().Interface().(encoding.TextMarshaler)})) + } + va = v2 + } } v := newAddressableValue(va.Elem().Type()) v.Set(va.Elem()) @@ -1593,6 +1715,25 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { return newInvalidFormatError(dec, t, uo.Format) } + if uo.Flags.Get(jsonflags.MergeWithLegacySemantics) && !va.IsNil() { + // Legacy merge behavior is difficult to explain. + // In general, it only merges for non-nil pointer kinds. + // As a special case, unmarshaling a JSON null into a pointer + // sets a concrete nil pointer of the underlying type + // (rather than setting the interface value itself to nil). + e := va.Elem() + if e.Kind() == reflect.Pointer && !e.IsNil() { + if dec.PeekKind() == 'n' && e.Elem().Kind() == reflect.Pointer { + if _, err := dec.ReadToken(); err != nil { + return err + } + va.Elem().Elem().SetZero() + return nil + } + } else { + va.SetZero() + } + } if dec.PeekKind() == 'n' { if _, err := dec.ReadToken(); err != nil { return err @@ -1629,7 +1770,11 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { case '"': v = newAddressableValue(stringType) case '0': - v = newAddressableValue(float64Type) + if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) { + v = addressableValue{reflect.ValueOf(internal.NewRawNumber()).Elem(), true} + } else { + v = newAddressableValue(float64Type) + } case '{': v = newAddressableValue(mapStringAnyType) case '[': diff --git a/vendor/github.com/go-json-experiment/json/arshal_funcs.go b/vendor/github.com/go-json-experiment/json/arshal_funcs.go index 50c7489..84bee86 100644 --- a/vendor/github.com/go-json-experiment/json/arshal_funcs.go +++ b/vendor/github.com/go-json-experiment/json/arshal_funcs.go @@ -10,6 +10,7 @@ import ( "reflect" "sync" + "github.com/go-json-experiment/json/internal" "github.com/go-json-experiment/json/internal/jsonflags" "github.com/go-json-experiment/json/internal/jsonopts" "github.com/go-json-experiment/json/jsontext" @@ -177,6 +178,9 @@ 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 export.Encoder(enc).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFuncV1") // unlike unmarshal, always wrapped + } err = newMarshalErrorBefore(enc, t, err) return collapseSemanticErrors(err) } @@ -226,6 +230,9 @@ func MarshalFuncV2[T any](fn func(*jsontext.Encoder, T, Options) error) *Marshal } err = errSkipMutation } + if xe.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFuncV2") // unlike unmarshal, always wrapped + } if !export.IsIOError(err) { err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err) } @@ -260,6 +267,9 @@ 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 export.Decoder(dec).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return err // unlike marshal, never wrapped + } err = newUnmarshalErrorAfter(dec, t, err) return collapseSemanticErrors(err) } @@ -302,6 +312,9 @@ func UnmarshalFuncV2[T any](fn func(*jsontext.Decoder, T, Options) error) *Unmar } err = errSkipMutation } + if export.Decoder(dec).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return err // unlike marshal, never wrapped + } if !isSyntacticError(err) && !export.IsIOError(err) { err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err) } diff --git a/vendor/github.com/go-json-experiment/json/arshal_inlined.go b/vendor/github.com/go-json-experiment/json/arshal_inlined.go index 8391717..f6b97db 100644 --- a/vendor/github.com/go-json-experiment/json/arshal_inlined.go +++ b/vendor/github.com/go-json-experiment/json/arshal_inlined.go @@ -35,7 +35,7 @@ var jsontextValueType = reflect.TypeFor[jsontext.Value]() // marshalInlinedFallbackAll marshals all the members in an inlined fallback. func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct, f *structField, insertUnquotedName func([]byte) bool) error { - v := addressableValue{va.Field(f.index[0])} // addressable if struct value is addressable + v := addressableValue{va.Field(f.index[0]), va.forcedAddr} // addressable if struct value is addressable if len(f.index) > 1 { v = v.fieldByIndex(f.index[1:], false) if !v.IsValid() { @@ -103,12 +103,12 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j } return nil } else { - m := v // must be a map[string]V + m := v // must be a map[~string]V n := m.Len() if n == 0 { return nil } - mk := newAddressableValue(stringType) + mk := newAddressableValue(m.Type().Key()) mv := newAddressableValue(m.Type().Elem()) marshalKey := func(mk addressableValue) error { xe := export.Encoder(enc) @@ -166,7 +166,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j // unmarshalInlinedFallbackNext unmarshals only the next member in an inlined fallback. func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct, f *structField, quotedName, unquotedName []byte) error { - v := addressableValue{va.Field(f.index[0])} // addressable if struct value is addressable + v := addressableValue{va.Field(f.index[0]), va.forcedAddr} // addressable if struct value is addressable if len(f.index) > 1 { v = v.fieldByIndex(f.index[1:], true) } @@ -202,12 +202,15 @@ func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo } else { name := string(unquotedName) // TODO: Intern this? - m := v // must be a map[string]V + m := v // must be a map[~string]V if m.IsNil() { m.Set(reflect.MakeMap(m.Type())) } mk := reflect.ValueOf(name) - mv := newAddressableValue(v.Type().Elem()) // TODO: Cache across calls? + if mkt := m.Type().Key(); mkt != stringType { + mk = mk.Convert(mkt) + } + mv := newAddressableValue(m.Type().Elem()) // TODO: Cache across calls? if v2 := m.MapIndex(mk); v2.IsValid() { mv.Set(v2) } diff --git a/vendor/github.com/go-json-experiment/json/arshal_methods.go b/vendor/github.com/go-json-experiment/json/arshal_methods.go index d37d0f4..1a5dfec 100644 --- a/vendor/github.com/go-json-experiment/json/arshal_methods.go +++ b/vendor/github.com/go-json-experiment/json/arshal_methods.go @@ -9,6 +9,7 @@ import ( "errors" "reflect" + "github.com/go-json-experiment/json/internal" "github.com/go-json-experiment/json/internal/jsonflags" "github.com/go-json-experiment/json/internal/jsonopts" "github.com/go-json-experiment/json/internal/jsonwire" @@ -31,6 +32,10 @@ var ( // This exists for now to provide performance benefits to netip types. // There is no semantic difference with this change. appenderToType = reflect.TypeFor[interface{ AppendTo([]byte) []byte }]() + + allMarshalerTypes = []reflect.Type{jsonMarshalerV2Type, jsonMarshalerV1Type, textAppenderType, textMarshalerType} + allUnmarshalerTypes = []reflect.Type{jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType} + allMethodTypes = append(allMarshalerTypes, allUnmarshalerTypes...) ) // TODO(https://go.dev/issue/62384): Use encoding.TextAppender instead @@ -108,53 +113,61 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { return fncs } - // Handle custom marshaler. - switch which := implementsWhich(t, jsonMarshalerV2Type, jsonMarshalerV1Type, textAppenderType, textMarshalerType); which { - case jsonMarshalerV2Type: + if needAddr, ok := implements(t, textMarshalerType); ok { fncs.nonDefault = true + prevMarshal := fncs.marshal fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { - xe := export.Encoder(enc) - prevDepth, prevLength := xe.Tokens.DepthLength() - xe.Flags.Set(jsonflags.WithinArshalCall | 1) - err := va.Addr().Interface().(MarshalerV2).MarshalJSONV2(enc, mo) - xe.Flags.Set(jsonflags.WithinArshalCall | 0) - currDepth, currLength := xe.Tokens.DepthLength() - if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil { - err = errNonSingularValue + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + (needAddr && va.forcedAddr) { + return prevMarshal(enc, va, mo) } - if err != nil { + marshaler := va.Addr().Interface().(encoding.TextMarshaler) + if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) { + b2, err := marshaler.MarshalText() + return append(b, b2...), err + }); err != nil { err = wrapSkipFunc(err, "marshal method") - if !export.IsIOError(err) { - err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err) + if export.Encoder(enc).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped + } + if !isSemanticError(err) && !export.IsIOError(err) { + err = newMarshalErrorBefore(enc, t, err) } return err } return nil } - case jsonMarshalerV1Type: - fncs.nonDefault = true - fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { - marshaler := va.Addr().Interface().(MarshalerV1) - val, err := marshaler.MarshalJSON() - if err != nil { - err = wrapSkipFunc(err, "marshal method") - err = newMarshalErrorBefore(enc, t, err) - return collapseSemanticErrors(err) - } - if err := enc.WriteValue(val); err != nil { - if isSyntacticError(err) { - err = newMarshalErrorBefore(enc, t, err) + // TODO(https://go.dev/issue/62384): Rely on encoding.TextAppender instead. + if implementsAny(t, appenderToType) && t.PkgPath() == "net/netip" { + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + appender := va.Addr().Interface().(interface{ AppendTo([]byte) []byte }) + if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) { + return appender.AppendTo(b), nil + }); err != nil { + if !isSemanticError(err) && !export.IsIOError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err } - return err + return nil } - return nil } - case textAppenderType: + } + + if needAddr, ok := implements(t, textAppenderType); ok { fncs.nonDefault = true + prevMarshal := fncs.marshal fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + (needAddr && va.forcedAddr) { + return prevMarshal(enc, va, mo) + } appender := va.Addr().Interface().(encodingTextAppender) if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil { err = wrapSkipFunc(err, "append method") + if export.Encoder(enc).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped + } if !isSemanticError(err) && !export.IsIOError(err) { err = newMarshalErrorBefore(enc, t, err) } @@ -162,65 +175,109 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { } return nil } - case textMarshalerType: + } + + if needAddr, ok := implements(t, jsonMarshalerV1Type); ok { fncs.nonDefault = true + prevMarshal := fncs.marshal fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { - marshaler := va.Addr().Interface().(encoding.TextMarshaler) - if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) { - b2, err := marshaler.MarshalText() - return append(b, b2...), err - }); err != nil { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + ((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) { + return prevMarshal(enc, va, mo) + } + marshaler := va.Addr().Interface().(MarshalerV1) + val, err := marshaler.MarshalJSON() + if err != nil { err = wrapSkipFunc(err, "marshal method") - if !isSemanticError(err) && !export.IsIOError(err) { + if export.Encoder(enc).Flags.Get(jsonflags.ReportLegacyErrorValues) { + 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 isSyntacticError(err) { err = newMarshalErrorBefore(enc, t, err) } return err } return nil } - // TODO(https://go.dev/issue/62384): Rely on encoding.TextAppender instead. - if implementsWhich(t, appenderToType) != nil && t.PkgPath() == "net/netip" { - fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { - appender := va.Addr().Interface().(interface{ AppendTo([]byte) []byte }) - if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) { - return appender.AppendTo(b), nil - }); err != nil { - if !isSemanticError(err) && !export.IsIOError(err) { - err = newMarshalErrorBefore(enc, t, err) - } - return err + } + + if needAddr, ok := implements(t, jsonMarshalerV2Type); ok { + fncs.nonDefault = true + prevMarshal := fncs.marshal + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + ((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) { + return prevMarshal(enc, va, mo) + } + xe := export.Encoder(enc) + prevDepth, prevLength := xe.Tokens.DepthLength() + xe.Flags.Set(jsonflags.WithinArshalCall | 1) + err := va.Addr().Interface().(MarshalerV2).MarshalJSONV2(enc, mo) + xe.Flags.Set(jsonflags.WithinArshalCall | 0) + currDepth, currLength := xe.Tokens.DepthLength() + if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil { + err = errNonSingularValue + } + if err != nil { + err = wrapSkipFunc(err, "marshal method") + if xe.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONV2") // unlike unmarshal, always wrapped } - return nil + if !export.IsIOError(err) { + err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err) + } + return err } + return nil } } - // Handle custom unmarshaler. - switch which := implementsWhich(t, jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType); which { - case jsonUnmarshalerV2Type: + if _, ok := implements(t, textUnmarshalerType); ok { fncs.nonDefault = true fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { xd := export.Decoder(dec) - prevDepth, prevLength := xd.Tokens.DepthLength() - xd.Flags.Set(jsonflags.WithinArshalCall | 1) - err := va.Addr().Interface().(UnmarshalerV2).UnmarshalJSONV2(dec, uo) - xd.Flags.Set(jsonflags.WithinArshalCall | 0) - currDepth, currLength := xd.Tokens.DepthLength() - if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil { - err = errNonSingularValue - } + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) if err != nil { + return err // must be a syntactic or I/O error + } + if val.Kind() == 'n' { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetZero() + } + return nil + } + if val.Kind() != '"' { + return newUnmarshalErrorAfter(dec, t, errNonStringValue) + } + s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler) + if err := unmarshaler.UnmarshalText(s); err != nil { err = wrapSkipFunc(err, "unmarshal method") - if !isSyntacticError(err) && !export.IsIOError(err) { - err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err) + if export.Decoder(dec).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return err // unlike marshal, never wrapped + } + if !isSemanticError(err) && !isSyntacticError(err) && !export.IsIOError(err) { + err = newUnmarshalErrorAfter(dec, t, err) } return err } return nil } - case jsonUnmarshalerV1Type: + } + + if _, ok := implements(t, jsonUnmarshalerV1Type); ok { fncs.nonDefault = true + prevUnmarshal := fncs.unmarshal fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + export.Decoder(dec).Tokens.Last.NeedObjectName() { + return prevUnmarshal(dec, va, uo) + } val, err := dec.ReadValue() if err != nil { return err // must be a syntactic or I/O error @@ -228,29 +285,40 @@ 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 export.Decoder(dec).Flags.Get(jsonflags.ReportLegacyErrorValues) { + return err // unlike marshal, never wrapped + } err = newUnmarshalErrorAfter(dec, t, err) return collapseSemanticErrors(err) } return nil } - case textUnmarshalerType: + } + + if _, ok := implements(t, jsonUnmarshalerV2Type); ok { fncs.nonDefault = true + prevUnmarshal := fncs.unmarshal fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { - xd := export.Decoder(dec) - var flags jsonwire.ValueFlags - val, err := xd.ReadValue(&flags) - if err != nil { - return err // must be a syntactic or I/O error + if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + export.Decoder(dec).Tokens.Last.NeedObjectName() { + return prevUnmarshal(dec, va, uo) } - if val.Kind() != '"' { - return newUnmarshalErrorAfter(dec, t, errNonStringValue) + xd := export.Decoder(dec) + prevDepth, prevLength := xd.Tokens.DepthLength() + xd.Flags.Set(jsonflags.WithinArshalCall | 1) + err := va.Addr().Interface().(UnmarshalerV2).UnmarshalJSONV2(dec, uo) + xd.Flags.Set(jsonflags.WithinArshalCall | 0) + currDepth, currLength := xd.Tokens.DepthLength() + if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil { + err = errNonSingularValue } - s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) - unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler) - if err := unmarshaler.UnmarshalText(s); err != nil { + if err != nil { err = wrapSkipFunc(err, "unmarshal method") - if !isSemanticError(err) && !isSyntacticError(err) && !export.IsIOError(err) { - err = newUnmarshalErrorAfter(dec, t, err) + if xd.Flags.Get(jsonflags.ReportLegacyErrorValues) { + return err // unlike marshal, never wrapped + } + if !isSyntacticError(err) && !export.IsIOError(err) { + err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err) } return err } @@ -261,13 +329,28 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { return fncs } -// implementsWhich is like t.Implements(ifaceType) for a list of interfaces, +// implementsAny is like t.Implements(ifaceType) for a list of interfaces, // but checks whether either t or reflect.PointerTo(t) implements the interface. -func implementsWhich(t reflect.Type, ifaceTypes ...reflect.Type) (which reflect.Type) { +func implementsAny(t reflect.Type, ifaceTypes ...reflect.Type) bool { for _, ifaceType := range ifaceTypes { - if t.Implements(ifaceType) || reflect.PointerTo(t).Implements(ifaceType) { - return ifaceType + if _, ok := implements(t, ifaceType); ok { + return true } } - return nil + return false +} + +// implements is like t.Implements(ifaceType) but checks whether +// either t or reflect.PointerTo(t) implements the interface. +// It also reports whether the value needs to be addressed +// in order to satisfy the interface. +func implements(t, ifaceType reflect.Type) (needAddr, ok bool) { + switch { + case t.Implements(ifaceType): + return false, true + case reflect.PointerTo(t).Implements(ifaceType): + return true, true + default: + return false, false + } } diff --git a/vendor/github.com/go-json-experiment/json/arshal_time.go b/vendor/github.com/go-json-experiment/json/arshal_time.go index 5a58bfb..cc4e2c5 100644 --- a/vendor/github.com/go-json-experiment/json/arshal_time.go +++ b/vendor/github.com/go-json-experiment/json/arshal_time.go @@ -82,7 +82,9 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { } switch k := val.Kind(); k { case 'n': - *td = time.Duration(0) + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + *td = time.Duration(0) + } return nil case '"': if u.isNumeric() && !uo.Flags.Get(jsonflags.StringifyNumbers) { @@ -145,7 +147,9 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { } switch k := val.Kind(); k { case 'n': - *tt = time.Time{} + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + *tt = time.Time{} + } return nil case '"': if u.isNumeric() && !uo.Flags.Get(jsonflags.StringifyNumbers) { diff --git a/vendor/github.com/go-json-experiment/json/doc.go b/vendor/github.com/go-json-experiment/json/doc.go index 6999717..442dd7e 100644 --- a/vendor/github.com/go-json-experiment/json/doc.go +++ b/vendor/github.com/go-json-experiment/json/doc.go @@ -107,9 +107,9 @@ // A Go embedded field is implicitly inlined unless an explicit JSON name // is specified. The inlined field must be a Go struct // (that does not implement any JSON methods), [jsontext.Value], -// map[string]T, or an unnamed pointer to such types. When marshaling, +// map[~string]T, or an unnamed pointer to such types. When marshaling, // inlined fields from a pointer type are omitted if it is nil. -// Inlined fields of type [jsontext.Value] and map[string]T are called +// Inlined fields of type [jsontext.Value] and map[~string]T are called // “inlined fallbacks” as they can represent all possible // JSON object members not directly handled by the parent struct. // Only one inlined fallback field may be specified in a struct, @@ -119,7 +119,7 @@ // - unknown: The "unknown" option is a specialized variant // of the inlined fallback to indicate that this Go struct field // contains any number of unknown JSON object members. The field type must -// be a [jsontext.Value], map[string]T, or an unnamed pointer to such types. +// be a [jsontext.Value], map[~string]T, or an unnamed pointer to such types. // If [DiscardUnknownMembers] is specified when marshaling, // the contents of this field are ignored. // If [RejectUnknownMembers] is specified when unmarshaling, diff --git a/vendor/github.com/go-json-experiment/json/errors.go b/vendor/github.com/go-json-experiment/json/errors.go index d96811c..3e3d95a 100644 --- a/vendor/github.com/go-json-experiment/json/errors.go +++ b/vendor/github.com/go-json-experiment/json/errors.go @@ -64,6 +64,9 @@ type SemanticError struct { // JSONKind is the JSON kind that could not be handled. JSONKind jsontext.Kind // may be zero if unknown + // JSONValue is the JSON number or string that could not be unmarshaled. + // It is not populated during marshaling. + JSONValue jsontext.Value // may be nil if irrelevant or unknown // GoType is the Go type that could not be handled. GoType reflect.Type // may be nil if unknown @@ -97,11 +100,12 @@ func newMarshalErrorBefore(e *jsontext.Encoder, t reflect.Type, err error) error // newUnmarshalErrorBefore wraps err in a SemanticError assuming that d // is positioned right before the next token or value, which causes an error. +// It does not record the next JSON kind as this error is used to indicate +// the receiving Go value is invalid to unmarshal into (and not a JSON error). func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) error { return &SemanticError{action: "unmarshal", GoType: t, Err: err, ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()), - JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1)), - JSONKind: d.PeekKind()} + JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1))} } // newUnmarshalErrorAfter wraps err in a SemanticError assuming that d @@ -114,6 +118,17 @@ func newUnmarshalErrorAfter(d *jsontext.Decoder, t reflect.Type, err error) erro JSONKind: jsontext.Value(tokOrVal).Kind()} } +// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d +// is positioned right after the previous token or value, which caused an error. +// It also stores a copy of the last JSON value if it is a string or number. +func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err error) error { + serr := newUnmarshalErrorAfter(d, t, err).(*SemanticError) + if serr.JSONKind == '"' || serr.JSONKind == '0' { + serr.JSONValue = jsontext.Value(export.Decoder(d).PreviousTokenOrValue()).Clone() + } + return serr +} + // 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 @@ -274,6 +289,10 @@ func (e *SemanticError) Error() string { preposition = "" } } + if len(e.JSONValue) > 0 && len(e.JSONValue) < 100 { + sb.WriteByte(' ') + sb.Write(e.JSONValue) + } // Format Go type. if e.GoType != nil { diff --git a/vendor/github.com/go-json-experiment/json/fields.go b/vendor/github.com/go-json-experiment/json/fields.go index 9d815a6..59823d4 100644 --- a/vendor/github.com/go-json-experiment/json/fields.go +++ b/vendor/github.com/go-json-experiment/json/fields.go @@ -51,7 +51,11 @@ type structField struct { var errNoExportedFields = errors.New("Go struct has no exported fields") -func makeStructFields(root reflect.Type) (structFields, *SemanticError) { +func makeStructFields(root reflect.Type) (sf structFields, serr *SemanticError) { + orErrorf := func(serr *SemanticError, t reflect.Type, f string, a ...any) *SemanticError { + return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)}) + } + // Setup a queue for a breath-first search. var queueIndex int type queueEntry struct { @@ -80,8 +84,9 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { hasAnyJSONTag = hasAnyJSONTag || hasTag options, ignored, err := parseFieldOptions(sf) if err != nil { - return structFields{}, &SemanticError{GoType: t, Err: err} - } else if ignored { + serr = cmp.Or(serr, &SemanticError{GoType: t, Err: err}) + } + if ignored { continue } hasAnyJSONField = true @@ -94,52 +99,50 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { fieldOptions: options, } if sf.Anonymous && !f.hasName { - f.inline = true // implied by use of Go embedding without an explicit name + if indirectType(f.typ).Kind() != reflect.Struct { + serr = orErrorf(serr, t, "embedded Go struct field %s of non-struct type must be explicitly given a JSON name", sf.Name) + } else { + f.inline = true // implied by use of Go embedding without an explicit name + } } if f.inline || f.unknown { // Handle an inlined field that serializes to/from // zero or more JSON object members. - if f.inline && f.unknown { - err := fmt.Errorf("Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name) - return structFields{}, &SemanticError{GoType: t, Err: err} - } switch f.fieldOptions { case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}: case fieldOptions{name: f.name, quotedName: f.quotedName, unknown: true}: + case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true, unknown: true}: + serr = orErrorf(serr, t, "Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name) + f.inline = false // let `unknown` take precedence default: - err := fmt.Errorf("Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name) - return structFields{}, &SemanticError{GoType: t, Err: err} + serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name) + continue // invalid inlined field; treat as ignored } - // Unwrap one level of pointer indirection similar to how Go - // only allows embedding either T or *T, but not **T. - tf := f.typ - if tf.Kind() == reflect.Pointer && tf.Name() == "" { - tf = tf.Elem() - } // Reject any types with custom serialization otherwise // it becomes impossible to know what sub-fields to inline. - if which := implementsWhich(tf, - jsonMarshalerV2Type, jsonMarshalerV1Type, textMarshalerType, - jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType, - ); which != nil && tf != jsontextValueType { - err := fmt.Errorf("inlined Go struct field %s of type %s must not implement JSON marshal or unmarshal methods", sf.Name, tf) - return structFields{}, &SemanticError{GoType: t, Err: err} + tf := indirectType(f.typ) + if implementsAny(tf, allMethodTypes...) && tf != jsontextValueType { + serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must not implement marshal or unmarshal methods", sf.Name, tf) + continue // invalid inlined field; treat as ignored } // Handle an inlined field that serializes to/from // a finite number of JSON object members backed by a Go struct. if tf.Kind() == reflect.Struct { if f.unknown { - err := fmt.Errorf("inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf) - return structFields{}, &SemanticError{GoType: t, Err: err} + serr = orErrorf(serr, t, "inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf) + continue // invalid inlined field; treat as ignored } if qe.visitChildren { queue = append(queue, queueEntry{tf, f.index, !seen[tf]}) } seen[tf] = true continue + } else if !sf.IsExported() { + serr = orErrorf(serr, t, "inlined Go struct field %s is not exported", sf.Name) + continue // invalid inlined field; treat as ignored } // Handle an inlined field that serializes to/from any number of @@ -147,17 +150,22 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { switch { case tf == jsontextValueType: f.fncs = nil // specially handled in arshal_inlined.go - case tf.Kind() == reflect.Map && tf.Key() == stringType: + case tf.Kind() == reflect.Map && tf.Key().Kind() == reflect.String: + if implementsAny(tf.Key(), allMethodTypes...) { + serr = orErrorf(serr, t, "inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods", sf.Name, tf) + continue // invalid inlined field; treat as ignored + } f.fncs = lookupArshaler(tf.Elem()) default: - err := fmt.Errorf("inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf) - return structFields{}, &SemanticError{GoType: t, Err: err} + serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf) + continue // invalid inlined field; treat as ignored } // Reject multiple inlined fallback fields within the same struct. if inlinedFallbackIndex >= 0 { - err := fmt.Errorf("inlined Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(inlinedFallbackIndex).Name, sf.Name) - return structFields{}, &SemanticError{GoType: t, Err: err} + serr = orErrorf(serr, t, "inlined Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(inlinedFallbackIndex).Name, sf.Name) + // Still append f to inlinedFallbacks as there is still a + // check for a dominant inlined fallback before returning. } inlinedFallbackIndex = i @@ -166,6 +174,24 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { // Handle normal Go struct field that serializes to/from // a single JSON object member. + // Unexported fields cannot be serialized except for + // embedded fields of a struct type, + // which might promote exported fields of their own. + if !sf.IsExported() { + tf := indirectType(f.typ) + if !(sf.Anonymous && tf.Kind() == reflect.Struct) { + serr = orErrorf(serr, t, "Go struct field %s is not exported", sf.Name) + continue + } + // Unfortunately, methods on the unexported field + // still cannot be called. + if implementsAny(tf, allMethodTypes...) || + (f.omitzero && implementsAny(tf, isZeroerType)) { + serr = orErrorf(serr, t, "Go struct field %s is not exported for method calls", sf.Name) + continue + } + } + // Provide a function that uses a type's IsZero method. switch { case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType): @@ -194,15 +220,11 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { f.isEmpty = func(va addressableValue) bool { return va.IsNil() } } - // Reject user-specified names with invalid UTF-8. - if !utf8.ValidString(f.name) { - err := fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, f.name) - return structFields{}, &SemanticError{GoType: t, Err: err} - } // Reject multiple fields with same name within the same struct. if j, ok := namesIndex[f.name]; ok { - err := fmt.Errorf("Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name) - return structFields{}, &SemanticError{GoType: t, Err: err} + serr = orErrorf(serr, t, "Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name) + // Still append f to allFields as there is still a + // check for a dominant field before returning. } namesIndex[f.name] = i @@ -223,7 +245,7 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { // errors returned by errors.New would fail to serialize. isEmptyStruct := t.NumField() == 0 if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField { - return structFields{}, &SemanticError{GoType: t, Err: errNoExportedFields} + serr = cmp.Or(serr, &SemanticError{GoType: t, Err: errNoExportedFields}) } } @@ -235,19 +257,11 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { // or the one that is uniquely tagged with a JSON name. // Otherwise, no dominant field exists for the set. flattened := allFields[:0] - slices.SortFunc(allFields, func(x, y structField) int { - switch { - case x.name != y.name: - return strings.Compare(x.name, y.name) - case len(x.index) != len(y.index): - return cmp.Compare(len(x.index), len(y.index)) - case x.hasName && !y.hasName: - return -1 - case !x.hasName && y.hasName: - return +1 - default: - return 0 // TODO(https://go.dev/issue/61643): Compare bools better. - } + slices.SortStableFunc(allFields, func(x, y structField) int { + return cmp.Or( + strings.Compare(x.name, y.name), + cmp.Compare(len(x.index), len(y.index)), + boolsCompare(!x.hasName, !y.hasName)) }) for len(allFields) > 0 { n := 1 // number of fields with the same exact name @@ -301,8 +315,17 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) { if n := len(inlinedFallbacks); n == 1 || (n > 1 && len(inlinedFallbacks[0].index) != len(inlinedFallbacks[1].index)) { fs.inlinedFallback = &inlinedFallbacks[0] // dominant inlined fallback field } + return fs, serr +} - return fs, nil +// indirectType unwraps one level of pointer indirection +// similar to how Go only allows embedding either T or *T, +// but not **T or P (which is a named pointer). +func indirectType(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Pointer && t.Name() == "" { + t = t.Elem() + } + return t } // matchFoldedName matches a case-insensitive name depending on the options. @@ -356,16 +379,16 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, return fieldOptions{}, true, nil } - // Check whether this field is unexported. - if !sf.IsExported() { - // In contrast to v1, v2 no longer forwards exported fields from - // embedded fields of unexported types since Go reflection does not - // allow the same set of operations that are available in normal cases - // of purely exported fields. - // See https://go.dev/issue/21357 and https://go.dev/issue/24153. - if sf.Anonymous { - err = cmp.Or(err, fmt.Errorf("embedded Go struct field %s of an unexported type must be explicitly ignored with a `json:\"-\"` tag", sf.Type.Name())) - } + // Check whether this field is unexported and not embedded, + // which Go reflection cannot mutate for the sake of serialization. + // + // An embedded field of an unexported type is still capable of + // forwarding exported fields, which may be JSON serialized. + // This technically operates on the edge of what is permissible by + // the Go language, but the most recent decision is to permit this. + // + // See https://go.dev/issue/24153 and https://go.dev/issue/32772. + if !sf.IsExported() && !sf.Anonymous { // Tag options specified on an unexported field suggests user error. if hasTag { err = cmp.Or(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag)) @@ -383,17 +406,26 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool { return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes })) - opt := tag[:n] - if n == 0 { - // Allow a single quoted string for arbitrary names. - var err2 error - opt, n, err2 = consumeTagOption(tag) + name := tag[:n] + + // If the next character is not a comma, then the name is either + // malformed (if n > 0) or a single-quoted name. + // In either case, call consumeTagOption to handle it further. + var err2 error + if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) { + name, n, err2 = consumeTagOption(tag) if err2 != nil { err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2)) } } - out.hasName = true - out.name = opt + if !utf8.ValidString(name) { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, name)) + name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError + } + if err2 == nil { + out.hasName = true + out.name = name + } tag = tag[n:] } b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{}) @@ -473,7 +505,7 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, // Reject duplicates. switch { case out.casing == nocase|strictcase: - err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `nocase` and `structcase` tag options", sf.Name)) + err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `nocase` and `strictcase` tag options", sf.Name)) case seenOpts[opt]: err = cmp.Or(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt)) } @@ -482,6 +514,10 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, return out, false, err } +// consumeTagOption consumes the next option, +// which is either a Go identifier or a single-quoted string. +// If the next option is invalid, it returns all of in until the next comma, +// and reports an error. func consumeTagOption(in string) (string, int, error) { // For legacy compatibility with v1, assume options are comma-separated. i := strings.IndexByte(in, ',') @@ -544,3 +580,15 @@ func consumeTagOption(in string) (string, int, error) { func isLetterOrDigit(r rune) bool { return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r) } + +// boolsCompare compares x and y, ordering false before true. +func boolsCompare(x, y bool) int { + switch { + case !x && y: + return -1 + default: + return 0 + case x && !y: + return +1 + } +} diff --git a/vendor/github.com/go-json-experiment/json/internal/internal.go b/vendor/github.com/go-json-experiment/json/internal/internal.go index cf020cd..bb36b03 100644 --- a/vendor/github.com/go-json-experiment/json/internal/internal.go +++ b/vendor/github.com/go-json-experiment/json/internal/internal.go @@ -4,6 +4,8 @@ package internal +import "errors" + // NotForPublicUse is a marker type that an API is for internal use only. // It does not perfectly prevent usage of that API, but helps to restrict usage. // Anything with this marker is not covered by the Go compatibility agreement. @@ -12,3 +14,26 @@ type NotForPublicUse struct{} // AllowInternalUse is passed from "json" to "jsontext" to authenticate // that the caller can have access to internal functionality. var AllowInternalUse NotForPublicUse + +// Sentinel error values internally shared between jsonv1 and jsonv2. +var ( + ErrCycle = errors.New("encountered a cycle") + ErrNonNilReference = errors.New("value must be passed as a non-nil pointer reference") +) + +var ( + // TransformMarshalError converts a v2 error into a v1 error. + // It is called only at the top-level of a Marshal function. + TransformMarshalError func(any, error) error + // NewMarshalerError constructs a jsonv1.MarshalerError. + // It is called after a user-defined Marshal method/function fails. + NewMarshalerError func(any, error, string) error + // TransformUnmarshalError converts a v2 error into a v1 error. + // It is called only at the top-level of a Unmarshal function. + TransformUnmarshalError func(any, error) error + + // NewRawNumber returns new(jsonv1.Number). + NewRawNumber func() any + // RawNumberOf returns jsonv1.Number(b). + RawNumberOf func(b []byte) any +) diff --git a/vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go b/vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go index 5c7c7f8..403156b 100644 --- a/vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go +++ b/vendor/github.com/go-json-experiment/json/internal/jsonflags/flags.go @@ -50,11 +50,14 @@ const ( AllowInvalidUTF8 | EscapeForHTML | EscapeForJS | + EscapeInvalidUTF8 | + PreserveRawStrings | Deterministic | FormatNilMapAsNull | FormatNilSliceAsNull | MatchCaseInsensitiveNames | - FormatByteArrayAsArray | + CallMethodsWithLegacySemantics | + FormatBytesWithLegacySemantics | FormatTimeDurationAsNanosecond | IgnoreStructErrors | MatchCaseSensitiveDelimiter | @@ -62,7 +65,6 @@ const ( OmitEmptyWithLegacyDefinition | RejectFloatOverflow | ReportLegacyErrorValues | - SkipUnaddressableMethods | StringifyWithLegacySemantics | UnmarshalArrayFromAnyLength @@ -83,10 +85,11 @@ const ( AllowInvalidUTF8 // encode or decode WithinArshalCall // encode or decode; for internal use by json.Marshal and json.Unmarshal OmitTopLevelNewline // encode only; for internal use by json.Marshal and json.MarshalWrite - PreserveRawStrings // encode only; for internal use by jsontext.Value.Canonicalize + PreserveRawStrings // encode only; exposed in v1 and also used by jsontext.Value.Canonicalize CanonicalizeNumbers // encode only; for internal use by jsontext.Value.Canonicalize EscapeForHTML // encode only EscapeForJS // encode only + EscapeInvalidUTF8 // encode only; only exposed in v1 Multiline // encode only SpaceAfterColon // encode only SpaceAfterComma // encode only @@ -120,7 +123,8 @@ const ( const ( _ Bools = (maxArshalV2Flag >> 1) << iota - FormatByteArrayAsArray // marshal or unmarshal + CallMethodsWithLegacySemantics // marshal or unmarshal + FormatBytesWithLegacySemantics // marshal or unmarshal FormatTimeDurationAsNanosecond // marshal or unmarshal IgnoreStructErrors // marshal or unmarshal MatchCaseSensitiveDelimiter // marshal or unmarshal @@ -128,7 +132,6 @@ const ( OmitEmptyWithLegacyDefinition // marshal RejectFloatOverflow // unmarshal ReportLegacyErrorValues // marshal or unmarshal - SkipUnaddressableMethods // 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 diff --git a/vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go b/vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go index c9ea707..366aa49 100644 --- a/vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go +++ b/vendor/github.com/go-json-experiment/json/internal/jsonwire/encode.go @@ -85,7 +85,11 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflag case r == utf8.RuneError && rn == 1: hasInvalidUTF8 = true dst = append(dst, src[i:n]...) - dst = append(dst, "\ufffd"...) + if flags.Get(jsonflags.EscapeInvalidUTF8) { + dst = append(dst, `\ufffd`...) + } else { + dst = append(dst, "\ufffd"...) + } n += rn i = n case (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS): @@ -150,18 +154,49 @@ func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error if err != nil { return dst, n, err } - isCanonical := !flags.Get(jsonflags.EscapeForHTML | jsonflags.EscapeForJS) - if flags.Get(jsonflags.PreserveRawStrings) || (isCanonical && valFlags.IsCanonical()) { + + // If the output requires no special escapes, and the input + // is already in canonical form or should be preserved verbatim, + // then directly copy the input to the output. + if !flags.Get(jsonflags.EscapeForHTML|jsonflags.EscapeForJS) && + (valFlags.IsCanonical() || flags.Get(jsonflags.PreserveRawStrings)) { dst = append(dst, src[:n]...) // copy the string verbatim return dst, n, nil } - // TODO: Implement a direct, raw-to-raw reformat for strings. - // If the escapeRune option would have resulted in no changes to the output, - // it would be faster to simply append src to dst without going through - // an intermediary representation in a separate buffer. + // If the input should be preserved verbatim, we still need to + // respect the EscapeForHTML and EscapeForJS options. + // Note that EscapeInvalidUTF8 is not respected. + // This logic ensures that pre-escaped sequences remained escaped. + if flags.Get(jsonflags.PreserveRawStrings) { + var i, lastAppendIndex int + for i < n { + if c := src[i]; c < utf8.RuneSelf { + if (c == '<' || c == '>' || c == '&') && flags.Get(jsonflags.EscapeForHTML) { + dst = append(dst, src[lastAppendIndex:i]...) + dst = appendEscapedASCII(dst, c) + lastAppendIndex = i + 1 + } + i++ + } else { + r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:])) + if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) { + dst = append(dst, src[lastAppendIndex:i]...) + dst = appendEscapedUnicode(dst, r) + lastAppendIndex = i + rn + } + i += rn + } + } + return append(dst, src[lastAppendIndex:n]...), n, nil + } + + // The input contains characters that might need escaping, + // unnecessary escape sequences, or invalid UTF-8. + // Perform a round-trip unquote and quote to properly reformat + // these sequences according the current flags. b, _ := AppendUnquote(nil, src[:n]) - dst, _ = AppendQuote(dst, string(b), flags) + dst, _ = AppendQuote(dst, b, flags) return dst, n, nil } diff --git a/vendor/github.com/go-json-experiment/json/jsontext/value.go b/vendor/github.com/go-json-experiment/json/jsontext/value.go index fd82b6a..97c97b1 100644 --- a/vendor/github.com/go-json-experiment/json/jsontext/value.go +++ b/vendor/github.com/go-json-experiment/json/jsontext/value.go @@ -150,6 +150,7 @@ func (v *Value) reformat(canonical, multiline bool, prefix, indent string) error eo.Flags.Set(jsonflags.PreserveRawStrings | 0) // per RFC 8785, section 3.2.2.2 eo.Flags.Set(jsonflags.EscapeForHTML | 0) // per RFC 8785, section 3.2.2.2 eo.Flags.Set(jsonflags.EscapeForJS | 0) // per RFC 8785, section 3.2.2.2 + eo.Flags.Set(jsonflags.EscapeInvalidUTF8 | 0) // per RFC 8785, section 3.2.2.2 eo.Flags.Set(jsonflags.Multiline | 0) // per RFC 8785, section 3.2.1 } else { if s := strings.TrimLeft(prefix, " \t"); len(s) > 0 { @@ -161,6 +162,8 @@ func (v *Value) reformat(canonical, multiline bool, prefix, indent string) error eo.Flags.Set(jsonflags.AllowInvalidUTF8 | 1) eo.Flags.Set(jsonflags.AllowDuplicateNames | 1) eo.Flags.Set(jsonflags.PreserveRawStrings | 1) + eo.Flags.Set(jsonflags.EscapeForHTML | 0) // ensure strings are preserved + eo.Flags.Set(jsonflags.EscapeForJS | 0) // ensure strings are preserved if multiline { eo.Flags.Set(jsonflags.Multiline | 1) eo.Flags.Set(jsonflags.SpaceAfterColon | 1) diff --git a/vendor/modules.txt b/vendor/modules.txt index b34d155..094468d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/go-json-experiment/json v0.0.0-20241215161817-a8b28468c6ae +# github.com/go-json-experiment/json v0.0.0-20241228065829-3e670832f349 ## explicit; go 1.23 github.com/go-json-experiment/json github.com/go-json-experiment/json/internal