aboutsummaryrefslogtreecommitdiff
path: root/src/encoding
diff options
context:
space:
mode:
Diffstat (limited to 'src/encoding')
-rw-r--r--src/encoding/json/v2/arshal.go4
-rw-r--r--src/encoding/json/v2/arshal_methods.go39
-rw-r--r--src/encoding/json/v2/arshal_test.go25
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: `{}`,