diff options
Diffstat (limited to 'src/encoding')
| -rw-r--r-- | src/encoding/json/v2/arshal.go | 4 | ||||
| -rw-r--r-- | src/encoding/json/v2/arshal_methods.go | 39 | ||||
| -rw-r--r-- | src/encoding/json/v2/arshal_test.go | 25 |
3 files changed, 55 insertions, 13 deletions
diff --git a/src/encoding/json/v2/arshal.go b/src/encoding/json/v2/arshal.go index 180a935c52..a3e96b7abf 100644 --- a/src/encoding/json/v2/arshal.go +++ b/src/encoding/json/v2/arshal.go @@ -55,6 +55,8 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse) // // - If the value type implements [MarshalerTo], // then the MarshalJSONTo method is called to encode the value. +// If the method returns [errors.ErrUnsupported], +// then the input is encoded according to subsequent rules. // // - If the value type implements [Marshaler], // then the MarshalJSON method is called to encode the value. @@ -270,6 +272,8 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro // // - If the value type implements [UnmarshalerFrom], // then the UnmarshalJSONFrom method is called to decode the JSON value. +// If the method returns [errors.ErrUnsupported], +// then the input is decoded according to subsequent rules. // // - If the value type implements [Unmarshaler], // then the UnmarshalJSON method is called to decode the JSON value. diff --git a/src/encoding/json/v2/arshal_methods.go b/src/encoding/json/v2/arshal_methods.go index ed65ed7632..3ac17baf1d 100644 --- a/src/encoding/json/v2/arshal_methods.go +++ b/src/encoding/json/v2/arshal_methods.go @@ -57,18 +57,22 @@ type Marshaler interface { // then MarshalerTo takes precedence. In such a case, both implementations // should aim to have equivalent behavior for the default marshal options. // -// The implementation must write only one JSON value to the Encoder and -// must not retain the pointer to [jsontext.Encoder]. +// The implementation must write only one JSON value to the Encoder. +// Alternatively, it may return [errors.ErrUnsupported] without mutating +// the Encoder. The "json" package calling the method will +// use the next available JSON representation for the receiver type. +// Implementations must not retain the pointer to [jsontext.Encoder]. // // If the returned error is a [SemanticError], then unpopulated fields // of the error may be populated by [json] with additional context. // Errors of other types are wrapped within a [SemanticError], // unless it is an IO error. +// +// The MarshalJSONTo method should not be directly called as it may +// return sentinel errors that need special handling. +// Users should instead call [MarshalEncode], which handles such cases. type MarshalerTo interface { MarshalJSONTo(*jsontext.Encoder) error - - // TODO: Should users call the MarshalEncode function or - // should/can they call this method directly? Does it matter? } // Unmarshaler is implemented by types that can unmarshal themselves. @@ -100,18 +104,21 @@ type Unmarshaler interface { // The implementation must read only one JSON value from the Decoder. // It is recommended that UnmarshalJSONFrom implement merge semantics when // unmarshaling into a pre-populated value. -// +// Alternatively, it may return [errors.ErrUnsupported] without mutating +// the Decoder. The "json" package calling the method will +// use the next available JSON representation for the receiver type. // Implementations must not retain the pointer to [jsontext.Decoder]. // // If the returned error is a [SemanticError], then unpopulated fields // of the error may be populated by [json] with additional context. // Errors of other types are wrapped within a [SemanticError], // unless it is a [jsontext.SyntacticError] or an IO error. +// +// The UnmarshalJSONFrom method should not be directly called as it may +// return sentinel errors that need special handling. +// Users should instead call [UnmarshalDecode], which handles such cases. type UnmarshalerFrom interface { UnmarshalJSONFrom(*jsontext.Decoder) error - - // TODO: Should users call the UnmarshalDecode function or - // should/can they call this method directly? Does it matter? } func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { @@ -221,7 +228,12 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { err = errNonSingularValue } if err != nil { - err = wrapErrUnsupported(err, "MarshalJSONTo method") + if errors.Is(err, errors.ErrUnsupported) { + if prevDepth == currDepth && prevLength == currLength { + return prevMarshal(enc, va, mo) + } + err = errUnsupportedMutation + } if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONTo") // unlike unmarshal, always wrapped } @@ -315,7 +327,12 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { err = errNonSingularValue } if err != nil { - err = wrapErrUnsupported(err, "UnmarshalJSONFrom method") + if errors.Is(err, errors.ErrUnsupported) { + if prevDepth == currDepth && prevLength == currLength { + return prevUnmarshal(dec, va, uo) + } + err = errUnsupportedMutation + } if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { return err2 diff --git a/src/encoding/json/v2/arshal_test.go b/src/encoding/json/v2/arshal_test.go index e8a9e79217..930595a9f7 100644 --- a/src/encoding/json/v2/arshal_test.go +++ b/src/encoding/json/v2/arshal_test.go @@ -529,6 +529,8 @@ type ( UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision } + unsupportedMethodJSONv2 map[string]int + structMethodJSONv2 struct{ value string } structMethodJSONv1 struct{ value string } structMethodText struct{ value string } @@ -609,6 +611,15 @@ func (p *allMethods) UnmarshalText(val []byte) error { return nil } +func (s *unsupportedMethodJSONv2) MarshalJSONTo(enc *jsontext.Encoder) error { + (*s)["called"] += 1 + return errors.ErrUnsupported +} +func (s *unsupportedMethodJSONv2) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + (*s)["called"] += 1 + return errors.ErrUnsupported +} + func (s structMethodJSONv2) MarshalJSONTo(enc *jsontext.Encoder) error { return enc.WriteToken(jsontext.String(s.value)) } @@ -3372,6 +3383,11 @@ func TestMarshal(t *testing.T) { want: `{"k1":"v1","k2":"v2"}`, canonicalize: true, }, { + name: jsontest.Name("Methods/JSONv2/ErrUnsupported"), + opts: []Options{Deterministic(true)}, + in: unsupportedMethodJSONv2{"fizz": 123}, + want: `{"called":1,"fizz":123}`, + }, { name: jsontest.Name("Methods/Invalid/JSONv2/Error"), in: marshalJSONv2Func(func(*jsontext.Encoder) error { return errSomeError @@ -3397,7 +3413,7 @@ func TestMarshal(t *testing.T) { in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { return errors.ErrUnsupported }), - wantErr: EM(errors.New("MarshalJSONTo method may not return errors.ErrUnsupported")).withType(0, T[marshalJSONv2Func]()), + wantErr: EM(nil).withType(0, T[marshalJSONv2Func]()), }, { name: jsontest.Name("Methods/Invalid/JSONv1/Error"), in: marshalJSONv1Func(func() ([]byte, error) { @@ -7777,6 +7793,11 @@ func TestUnmarshal(t *testing.T) { inVal: addr(map[structMethodText]string{{"k1"}: "v1a", {"k3"}: "v3"}), want: addr(map[structMethodText]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), }, { + name: jsontest.Name("Methods/JSONv2/ErrUnsupported"), + inBuf: `{"fizz":123}`, + inVal: addr(unsupportedMethodJSONv2{}), + want: addr(unsupportedMethodJSONv2{"called": 1, "fizz": 123}), + }, { name: jsontest.Name("Methods/Invalid/JSONv2/Error"), inBuf: `{}`, inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { @@ -7805,7 +7826,7 @@ func TestUnmarshal(t *testing.T) { inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { return errors.ErrUnsupported })), - wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "UnmarshalJSONFrom method")).withType(0, T[unmarshalJSONv2Func]()), + wantErr: EU(nil).withType(0, T[unmarshalJSONv2Func]()), }, { name: jsontest.Name("Methods/Invalid/JSONv1/Error"), inBuf: `{}`, |
