Skip to content

Commit 61beda3

Browse files
authored
Merge pull request #59 from golang/master
[pull] master from golang:master
2 parents 6319f88 + c7e6da5 commit 61beda3

8 files changed

Lines changed: 263 additions & 113 deletions

File tree

src/encoding/json/internal/jsonopts/options.go

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -114,70 +114,43 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
114114
var JoinUnknownOption = func(Struct, Options) Struct { panic("unknown option") }
115115

116116
func (dst *Struct) Join(srcs ...Options) {
117-
dst.join(false, srcs...)
118-
}
119-
120-
func (dst *Struct) JoinWithoutCoderOptions(srcs ...Options) {
121-
dst.join(true, srcs...)
122-
}
123-
124-
func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) {
125117
for _, src := range srcs {
126118
switch src := src.(type) {
127119
case nil:
128120
continue
129121
case jsonflags.Bools:
130-
if excludeCoderOptions {
131-
src &= ^jsonflags.AllCoderFlags
132-
}
133122
dst.Flags.Set(src)
134123
case Indent:
135-
if excludeCoderOptions {
136-
continue
137-
}
138124
dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1)
139125
dst.Indent = string(src)
140126
case IndentPrefix:
141-
if excludeCoderOptions {
142-
continue
143-
}
144127
dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1)
145128
dst.IndentPrefix = string(src)
146129
case ByteLimit:
147-
if excludeCoderOptions {
148-
continue
149-
}
150130
dst.Flags.Set(jsonflags.ByteLimit | 1)
151131
dst.ByteLimit = int64(src)
152132
case DepthLimit:
153-
if excludeCoderOptions {
154-
continue
155-
}
156133
dst.Flags.Set(jsonflags.DepthLimit | 1)
157134
dst.DepthLimit = int(src)
158135
case *Struct:
159-
srcFlags := src.Flags // shallow copy the flags
160-
if excludeCoderOptions {
161-
srcFlags.Clear(jsonflags.AllCoderFlags)
162-
}
163-
dst.Flags.Join(srcFlags)
164-
if srcFlags.Has(jsonflags.NonBooleanFlags) {
165-
if srcFlags.Has(jsonflags.Indent) {
136+
dst.Flags.Join(src.Flags)
137+
if src.Flags.Has(jsonflags.NonBooleanFlags) {
138+
if src.Flags.Has(jsonflags.Indent) {
166139
dst.Indent = src.Indent
167140
}
168-
if srcFlags.Has(jsonflags.IndentPrefix) {
141+
if src.Flags.Has(jsonflags.IndentPrefix) {
169142
dst.IndentPrefix = src.IndentPrefix
170143
}
171-
if srcFlags.Has(jsonflags.ByteLimit) {
144+
if src.Flags.Has(jsonflags.ByteLimit) {
172145
dst.ByteLimit = src.ByteLimit
173146
}
174-
if srcFlags.Has(jsonflags.DepthLimit) {
147+
if src.Flags.Has(jsonflags.DepthLimit) {
175148
dst.DepthLimit = src.DepthLimit
176149
}
177-
if srcFlags.Has(jsonflags.Marshalers) {
150+
if src.Flags.Has(jsonflags.Marshalers) {
178151
dst.Marshalers = src.Marshalers
179152
}
180-
if srcFlags.Has(jsonflags.Unmarshalers) {
153+
if src.Flags.Has(jsonflags.Unmarshalers) {
181154
dst.Unmarshalers = src.Unmarshalers
182155
}
183156
}
@@ -187,6 +160,28 @@ func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) {
187160
}
188161
}
189162

163+
// InitializeMultiline sets default options implied by Multiline.
164+
func (s *Struct) InitializeMultiline() {
165+
if !s.Flags.Has(jsonflags.SpaceAfterColon) {
166+
s.Flags.Set(jsonflags.SpaceAfterColon | 1)
167+
}
168+
if !s.Flags.Has(jsonflags.SpaceAfterComma) {
169+
s.Flags.Set(jsonflags.SpaceAfterComma | 0)
170+
}
171+
if !s.Flags.Has(jsonflags.Indent) {
172+
s.Flags.Set(jsonflags.Indent | 1)
173+
s.Indent = "\t"
174+
}
175+
}
176+
177+
// ChangedWhitespace reports whether whitespace values have changed.
178+
func ChangedWhitespace(s1, s2 Struct) bool {
179+
return s1.Flags.Get(jsonflags.Multiline) != s2.Flags.Get(jsonflags.Multiline) ||
180+
s1.Flags.Get(jsonflags.SpaceAfterColon) != s2.Flags.Get(jsonflags.SpaceAfterColon) ||
181+
s1.Flags.Get(jsonflags.SpaceAfterComma) != s2.Flags.Get(jsonflags.SpaceAfterComma) ||
182+
(s2.Flags.Get(jsonflags.Multiline) && (s1.Indent != s2.Indent || s1.IndentPrefix != s2.IndentPrefix))
183+
}
184+
190185
type (
191186
Indent string // jsontext.WithIndent
192187
IndentPrefix string // jsontext.WithIndentPrefix

src/encoding/json/internal/jsonopts/options_test.go

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ func makeFlags(f ...jsonflags.Bools) (fs jsonflags.Flags) {
2525

2626
func TestJoin(t *testing.T) {
2727
tests := []struct {
28-
in Options
29-
excludeCoders bool
30-
want *Struct
28+
in Options
29+
want *Struct
3130
}{{
3231
in: jsonflags.AllowInvalidUTF8 | 1,
3332
want: &Struct{Flags: makeFlags(jsonflags.AllowInvalidUTF8 | 1)},
@@ -68,46 +67,7 @@ func TestJoin(t *testing.T) {
6867
return &v2
6968
}(), // v2 fully replaces before (except for whitespace related flags)
7069
}, {
71-
in: jsonflags.Deterministic | jsonflags.AllowInvalidUTF8 | 1, excludeCoders: true,
72-
want: func() *Struct {
73-
v2 := DefaultOptionsV2
74-
v2.Flags.Set(jsonflags.Deterministic | 1)
75-
v2.Flags.Set(jsonflags.Indent | 1)
76-
v2.Flags.Set(jsonflags.Multiline | 0)
77-
v2.Indent = "\t"
78-
return &v2
79-
}(),
80-
}, {
81-
in: jsontext.WithIndentPrefix(" "), excludeCoders: true,
82-
want: func() *Struct {
83-
v2 := DefaultOptionsV2
84-
v2.Flags.Set(jsonflags.Deterministic | 1)
85-
v2.Flags.Set(jsonflags.Indent | 1)
86-
v2.Flags.Set(jsonflags.Multiline | 0)
87-
v2.Indent = "\t"
88-
return &v2
89-
}(),
90-
}, {
91-
in: jsontext.WithIndentPrefix(" "), excludeCoders: false,
92-
want: func() *Struct {
93-
v2 := DefaultOptionsV2
94-
v2.Flags.Set(jsonflags.Deterministic | 1)
95-
v2.Flags.Set(jsonflags.Indent | 1)
96-
v2.Flags.Set(jsonflags.IndentPrefix | 1)
97-
v2.Flags.Set(jsonflags.Multiline | 1)
98-
v2.Indent = "\t"
99-
v2.IndentPrefix = " "
100-
return &v2
101-
}(),
102-
}, {
103-
in: &Struct{
104-
Flags: jsonflags.Flags{
105-
Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix),
106-
Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix),
107-
},
108-
CoderValues: CoderValues{Indent: " ", IndentPrefix: " "},
109-
},
110-
excludeCoders: true,
70+
in: jsontext.WithIndentPrefix(" "),
11171
want: func() *Struct {
11272
v2 := DefaultOptionsV2
11373
v2.Flags.Set(jsonflags.Indent | 1)
@@ -125,7 +85,6 @@ func TestJoin(t *testing.T) {
12585
},
12686
CoderValues: CoderValues{Indent: " ", IndentPrefix: " "},
12787
},
128-
excludeCoders: false,
12988
want: func() *Struct {
13089
v2 := DefaultOptionsV2
13190
v2.Flags.Set(jsonflags.Indent | 1)
@@ -138,11 +97,7 @@ func TestJoin(t *testing.T) {
13897
}}
13998
got := new(Struct)
14099
for i, tt := range tests {
141-
if tt.excludeCoders {
142-
got.JoinWithoutCoderOptions(tt.in)
143-
} else {
144-
got.Join(tt.in)
145-
}
100+
got.Join(tt.in)
146101
if !reflect.DeepEqual(got, tt.want) {
147102
t.Fatalf("%d: Join:\n\tgot: %+v\n\twant: %+v", i, got, tt.want)
148103
}

src/encoding/json/jsontext/encode.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,8 @@ func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) {
124124
opts2 := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts
125125
opts2.Join(opts...)
126126
e.Struct = opts2
127-
if e.Flags.Get(jsonflags.Multiline) {
128-
if !e.Flags.Has(jsonflags.SpaceAfterColon) {
129-
e.Flags.Set(jsonflags.SpaceAfterColon | 1)
130-
}
131-
if !e.Flags.Has(jsonflags.SpaceAfterComma) {
132-
e.Flags.Set(jsonflags.SpaceAfterComma | 0)
133-
}
134-
if !e.Flags.Has(jsonflags.Indent) {
135-
e.Flags.Set(jsonflags.Indent | 1)
136-
e.Indent = "\t"
137-
}
127+
if e.Struct.Flags.Get(jsonflags.Multiline) {
128+
e.Struct.InitializeMultiline()
138129
}
139130
}
140131

src/encoding/json/v2/arshal.go

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package json
99
import (
1010
"bytes"
1111
"encoding"
12+
"errors"
1213
"io"
1314
"reflect"
1415
"sync"
@@ -30,6 +31,36 @@ var (
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.
3465
var 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.
168197
func 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

Comments
 (0)