@@ -9,6 +9,7 @@ package json
99import (
1010 "bytes"
1111 "encoding"
12+ "errors"
1213 "io"
1314 "reflect"
1415 "sync"
3031 _ time.Duration
3132)
3233
34+ var (
35+ // Once a JSON object has begun processing without duplicate name verification,
36+ // it does not track the history of names that have been seen so far.
37+ // Reject changing the setting for the current JSON object namespace,
38+ // otherwise we would be operating with inconsistent state.
39+ // Note that you can change the setting before processing the start
40+ // of a different JSON object.
41+ //
42+ // TODO: We could loosen this restriction in certain conditions.
43+ // If we are already checking for duplicate names,
44+ // we can momentarily disable it for the next JSON object member name.
45+ // However, if we are already NOT checking for duplicate names,
46+ // we cannot momentarily enable it for the next JSON object member name
47+ // since we already lack prior history of JSON object names.
48+ errChangingDuplicateNames = errors .New ("cannot change duplicate name checks after a JSON object has already begun processing" )
49+
50+ // The presence of invalid UTF-8 has an interesting intersection
51+ // with checking for duplicate names. Due to the semantic of mangling
52+ // invalid UTF-8 as the Unicode replacement character,
53+ // two string keys in a Go map (both with invalid UTF-8)
54+ // may encode as the same JSON string. Thus, we forbid changing of
55+ // invalid UTF-8 checks in the current JSON object namespace.
56+ errChangingInvalidUTF8 = errors .New ("cannot change UTF-8 checks after a JSON object has already begun processing" )
57+
58+ // TODO(https://go.dev/issue/79559): Changing whitespace currently
59+ // leads to strange effects and will need more careful adjustment.
60+ // For now, we just report an error.
61+ errChangingWhitespace = errors .New ("cannot change whitespace formatting within a MarshalEncode call" )
62+ )
63+
3364// export exposes internal functionality of the "jsontext" package.
3465var export = jsontext .Internal .Export (& internal .AllowInternalUse )
3566
@@ -158,19 +189,33 @@ func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
158189}
159190
160191// MarshalEncode serializes a Go value into an [jsontext.Encoder] according to
161- // the provided marshal options (while ignoring unmarshal, encode, or decode options).
162- // Any marshal-relevant options already specified on the [jsontext.Encoder]
163- // take lower precedence than the set of options provided by the caller.
164- // Unlike [Marshal] and [MarshalWrite], encode options are ignored because
165- // they must have already been specified on the provided [jsontext.Encoder].
192+ // the provided marshal or encode options (while ignoring unmarshal or decode options).
193+ // The options provided take precedence over options already applied on
194+ // the [jsontext.Encoder] and only apply for the duration of the marshal call.
166195//
167196// See [Marshal] for details about the conversion of a Go value into JSON.
168197func MarshalEncode (out * jsontext.Encoder , in any , opts ... Options ) (err error ) {
169198 xe := export .Encoder (out )
170199 if len (opts ) > 0 {
171200 optsOriginal := xe .Struct
172201 defer func () { xe .Struct = optsOriginal }()
173- xe .Struct .JoinWithoutCoderOptions (opts ... )
202+ xe .Struct .Join (opts ... )
203+ if xe .Tokens .Last .NeedObjectName () {
204+ if optsOriginal .Flags .Get (jsonflags .AllowDuplicateNames ) != xe .Struct .Flags .Get (jsonflags .AllowDuplicateNames ) {
205+ return newMarshalErrorBefore (out , reflect .TypeOf (in ), errChangingDuplicateNames )
206+ }
207+ if optsOriginal .Flags .Get (jsonflags .AllowInvalidUTF8 ) != xe .Struct .Flags .Get (jsonflags .AllowInvalidUTF8 ) {
208+ return newMarshalErrorBefore (out , reflect .TypeOf (in ), errChangingInvalidUTF8 )
209+ }
210+ }
211+ if xe .Struct .Flags .Has (jsonflags .AnyWhitespace ) {
212+ if xe .Struct .Flags .Get (jsonflags .Multiline ) {
213+ xe .Struct .InitializeMultiline ()
214+ }
215+ if jsonopts .ChangedWhitespace (optsOriginal , xe .Struct ) {
216+ return newMarshalErrorBefore (out , reflect .TypeOf (in ), errChangingWhitespace )
217+ }
218+ }
174219 }
175220 err = marshalEncode (out , in , & xe .Struct )
176221 if err != nil && xe .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
@@ -361,11 +406,9 @@ func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
361406}
362407
363408// UnmarshalDecode deserializes a Go value from a [jsontext.Decoder] according to
364- // the provided unmarshal options (while ignoring marshal, encode, or decode options).
365- // Any unmarshal options already specified on the [jsontext.Decoder]
366- // take lower precedence than the set of options provided by the caller.
367- // Unlike [Unmarshal] and [UnmarshalRead], decode options are ignored because
368- // they must have already been specified on the provided [jsontext.Decoder].
409+ // the provided unmarshal or decode options (while ignoring marshal or encode options).
410+ // The options provided take precedence over options already applied on
411+ // the [jsontext.Decoder] and only apply for the duration of the unmarshal call.
369412//
370413// The input may be a stream of zero or more JSON values,
371414// where this only unmarshals the next JSON value in the stream.
@@ -377,7 +420,15 @@ func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error)
377420 if len (opts ) > 0 {
378421 optsOriginal := xd .Struct
379422 defer func () { xd .Struct = optsOriginal }()
380- xd .Struct .JoinWithoutCoderOptions (opts ... )
423+ xd .Struct .Join (opts ... )
424+ if xd .Tokens .Last .NeedObjectName () {
425+ if optsOriginal .Flags .Get (jsonflags .AllowDuplicateNames ) != xd .Struct .Flags .Get (jsonflags .AllowDuplicateNames ) {
426+ return newUnmarshalErrorBefore (in , reflect .TypeOf (out ), errChangingDuplicateNames )
427+ }
428+ if optsOriginal .Flags .Get (jsonflags .AllowInvalidUTF8 ) != xd .Struct .Flags .Get (jsonflags .AllowInvalidUTF8 ) {
429+ return newUnmarshalErrorBefore (in , reflect .TypeOf (out ), errChangingInvalidUTF8 )
430+ }
431+ }
381432 }
382433 err = unmarshalDecode (in , out , & xd .Struct , false )
383434 if err != nil && xd .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
0 commit comments