Skip to content

Commit 9fd8f2c

Browse files
committed
Adjust single-line formatting behavior
Changes made: * Further separate the behavior of SpaceAfterColon and SpaceAfterComma from Multiline. That is, use of Multiline does not affect the behavior of SpaceAfterColon or SpaceAfterComma unless it has not been set. For example, specifying (SpaceAfterColon(false), Multiline(true)) should avoid the space after the colon even if we expect this combination of options to be seldom used. * Exclude whitespace formatting flags from DefaultOptionsV1 since v1 has API support for both single-line and multi-line output, so setting (or clearing) them cannot be classified as v1 behavior. Similarly, exclude whitespace formatting flags from DefaultOptionsV2. * Simplify the TestMarshal cases. * Add explicit TestEncoder cases to exercise all Encoder.WriteValue code paths.
1 parent b15d3ef commit 9fd8f2c

File tree

10 files changed

+114
-154
lines changed

10 files changed

+114
-154
lines changed

arshal_default.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -975,10 +975,9 @@ func makeStructArshaler(t reflect.Type) *arshaler {
975975
// Append any delimiters or optional whitespace.
976976
b := xe.Buf
977977
if xe.Tokens.Last.Length() > 0 {
978+
b = append(b, ',')
978979
if xe.Flags.Get(jsonflags.SpaceAfterComma) {
979-
b = append(b, ',', ' ')
980-
} else {
981-
b = append(b, ',')
980+
b = append(b, ' ')
982981
}
983982
}
984983
if xe.Flags.Get(jsonflags.Multiline) {

arshal_test.go

+10-80
Original file line numberDiff line numberDiff line change
@@ -1233,89 +1233,19 @@ func TestMarshal(t *testing.T) {
12331233
}`,
12341234
}, {
12351235
name: jsontest.Name("Structs/SpaceAfterColonAndComma"),
1236-
opts: []Options{
1237-
jsontext.SpaceAfterColon(true),
1238-
jsontext.SpaceAfterComma(true),
1239-
},
1240-
in: structAll{
1241-
Bool: true,
1242-
String: "hello",
1243-
Bytes: []byte{1, 2, 3},
1244-
Int: -64,
1245-
Uint: +64,
1246-
Float: 3.14159,
1247-
Map: map[string]string{"key": "value"},
1248-
StructScalars: structScalars{
1249-
Bool: true,
1250-
String: "hello",
1251-
Bytes: []byte{1, 2, 3},
1252-
Int: -64,
1253-
Uint: +64,
1254-
Float: 3.14159,
1255-
},
1256-
StructMaps: structMaps{
1257-
MapBool: map[string]bool{"": true},
1258-
MapString: map[string]string{"": "hello"},
1259-
MapBytes: map[string][]byte{"": {1, 2, 3}},
1260-
MapInt: map[string]int64{"": -64},
1261-
MapUint: map[string]uint64{"": +64},
1262-
MapFloat: map[string]float64{"": 3.14159},
1263-
},
1264-
StructSlices: structSlices{
1265-
SliceBool: []bool{true},
1266-
SliceString: []string{"hello"},
1267-
SliceBytes: [][]byte{{1, 2, 3}},
1268-
SliceInt: []int64{-64},
1269-
SliceUint: []uint64{+64},
1270-
SliceFloat: []float64{3.14159},
1271-
},
1272-
Slice: []string{"fizz", "buzz"},
1273-
Array: [1]string{"goodbye"},
1274-
Pointer: new(structAll),
1275-
Interface: (*structAll)(nil),
1276-
},
1277-
want: `{"Bool": true, "String": "hello", "Bytes": "AQID", "Int": -64, "Uint": 64, "Float": 3.14159, "Map": {"key": "value"}, "StructScalars": {"Bool": true, "String": "hello", "Bytes": "AQID", "Int": -64, "Uint": 64, "Float": 3.14159}, "StructMaps": {"MapBool": {"": true}, "MapString": {"": "hello"}, "MapBytes": {"": "AQID"}, "MapInt": {"": -64}, "MapUint": {"": 64}, "MapFloat": {"": 3.14159}}, "StructSlices": {"SliceBool": [true], "SliceString": ["hello"], "SliceBytes": ["AQID"], "SliceInt": [-64], "SliceUint": [64], "SliceFloat": [3.14159]}, "Slice": ["fizz", "buzz"], "Array": ["goodbye"], "Pointer": {"Bool": false, "String": "", "Bytes": "", "Int": 0, "Uint": 0, "Float": 0, "Map": {}, "StructScalars": {"Bool": false, "String": "", "Bytes": "", "Int": 0, "Uint": 0, "Float": 0}, "StructMaps": {"MapBool": {}, "MapString": {}, "MapBytes": {}, "MapInt": {}, "MapUint": {}, "MapFloat": {}}, "StructSlices": {"SliceBool": [], "SliceString": [], "SliceBytes": [], "SliceInt": [], "SliceUint": [], "SliceFloat": []}, "Slice": [], "Array": [""], "Pointer": null, "Interface": null}, "Interface": null}`,
1236+
opts: []Options{jsontext.SpaceAfterColon(true), jsontext.SpaceAfterComma(true)},
1237+
in: structOmitZeroAll{Int: 1, Uint: 1},
1238+
want: `{"Int": 1, "Uint": 1}`,
1239+
}, {
1240+
name: jsontest.Name("Structs/SpaceAfterColon"),
1241+
opts: []Options{jsontext.SpaceAfterColon(true)},
1242+
in: structOmitZeroAll{Int: 1, Uint: 1},
1243+
want: `{"Int": 1,"Uint": 1}`,
12781244
}, {
12791245
name: jsontest.Name("Structs/SpaceAfterComma"),
12801246
opts: []Options{jsontext.SpaceAfterComma(true)},
1281-
in: structAll{
1282-
Bool: true,
1283-
String: "hello",
1284-
Bytes: []byte{1, 2, 3},
1285-
Int: -64,
1286-
Uint: +64,
1287-
Float: 3.14159,
1288-
Map: map[string]string{"key": "value"},
1289-
StructScalars: structScalars{
1290-
Bool: true,
1291-
String: "hello",
1292-
Bytes: []byte{1, 2, 3},
1293-
Int: -64,
1294-
Uint: +64,
1295-
Float: 3.14159,
1296-
},
1297-
StructMaps: structMaps{
1298-
MapBool: map[string]bool{"": true},
1299-
MapString: map[string]string{"": "hello"},
1300-
MapBytes: map[string][]byte{"": {1, 2, 3}},
1301-
MapInt: map[string]int64{"": -64},
1302-
MapUint: map[string]uint64{"": +64},
1303-
MapFloat: map[string]float64{"": 3.14159},
1304-
},
1305-
StructSlices: structSlices{
1306-
SliceBool: []bool{true},
1307-
SliceString: []string{"hello"},
1308-
SliceBytes: [][]byte{{1, 2, 3}},
1309-
SliceInt: []int64{-64},
1310-
SliceUint: []uint64{+64},
1311-
SliceFloat: []float64{3.14159},
1312-
},
1313-
Slice: []string{"fizz", "buzz"},
1314-
Array: [1]string{"goodbye"},
1315-
Pointer: new(structAll),
1316-
Interface: (*structAll)(nil),
1317-
},
1318-
want: `{"Bool":true, "String":"hello", "Bytes":"AQID", "Int":-64, "Uint":64, "Float":3.14159, "Map":{"key":"value"}, "StructScalars":{"Bool":true, "String":"hello", "Bytes":"AQID", "Int":-64, "Uint":64, "Float":3.14159}, "StructMaps":{"MapBool":{"":true}, "MapString":{"":"hello"}, "MapBytes":{"":"AQID"}, "MapInt":{"":-64}, "MapUint":{"":64}, "MapFloat":{"":3.14159}}, "StructSlices":{"SliceBool":[true], "SliceString":["hello"], "SliceBytes":["AQID"], "SliceInt":[-64], "SliceUint":[64], "SliceFloat":[3.14159]}, "Slice":["fizz", "buzz"], "Array":["goodbye"], "Pointer":{"Bool":false, "String":"", "Bytes":"", "Int":0, "Uint":0, "Float":0, "Map":{}, "StructScalars":{"Bool":false, "String":"", "Bytes":"", "Int":0, "Uint":0, "Float":0}, "StructMaps":{"MapBool":{}, "MapString":{}, "MapBytes":{}, "MapInt":{}, "MapUint":{}, "MapFloat":{}}, "StructSlices":{"SliceBool":[], "SliceString":[], "SliceBytes":[], "SliceInt":[], "SliceUint":[], "SliceFloat":[]}, "Slice":[], "Array":[""], "Pointer":null, "Interface":null}, "Interface":null}`,
1247+
in: structOmitZeroAll{Int: 1, Uint: 1, Slice: []string{"a", "b"}},
1248+
want: `{"Int":1, "Uint":1, "Slice":["a", "b"]}`,
13191249
}, {
13201250
name: jsontest.Name("Structs/Stringified"),
13211251
opts: []Options{jsontext.Multiline(true)},

internal/jsonflags/flags.go

+5
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ const (
6868

6969
// AnyWhitespace reports whether the encoded output might have any whitespace.
7070
AnyWhitespace = Multiline | SpaceAfterColon | SpaceAfterComma
71+
72+
// WhitespaceFlags is the set of flags related to whitespace formatting.
73+
// In contrast to AnyWhitespace, this includes Indent and IndentPrefix
74+
// as those settings take no effect if Multiline is false.
75+
WhitespaceFlags = AnyWhitespace | Indent | IndentPrefix
7176
)
7277

7378
// Encoder and decoder flags.

internal/jsonopts/options.go

+2-14
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,17 @@ type ArshalValues struct {
4646
// DefaultOptionsV2 is the set of all options that define default v2 behavior.
4747
var DefaultOptionsV2 = Struct{
4848
Flags: jsonflags.Flags{
49-
Presence: uint64(jsonflags.AllFlags),
49+
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
5050
Values: uint64(0),
5151
},
52-
CoderValues: CoderValues{Indent: "\t"}, // Indent is set, but Multiline is set to false
5352
}
5453

5554
// DefaultOptionsV1 is the set of all options that define default v1 behavior.
5655
var DefaultOptionsV1 = Struct{
5756
Flags: jsonflags.Flags{
58-
Presence: uint64(jsonflags.AllFlags),
57+
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
5958
Values: uint64(jsonflags.DefaultV1Flags),
6059
},
61-
CoderValues: CoderValues{Indent: "\t"}, // Indent is set, but Multiline is set to false
6260
}
6361

6462
// CopyCoderOptions copies coder-specific options from src to dst.
@@ -130,22 +128,12 @@ func (dst *Struct) Join(srcs ...Options) {
130128
case nil:
131129
continue
132130
case jsonflags.Bools:
133-
switch src {
134-
case jsonflags.Multiline | 1:
135-
dst.Flags.Clear(jsonflags.SpaceAfterComma | jsonflags.SpaceAfterColon)
136-
case jsonflags.SpaceAfterComma | 1, jsonflags.SpaceAfterColon | 1:
137-
if dst.Flags.Get(jsonflags.Multiline) {
138-
continue
139-
}
140-
}
141131
dst.Flags.Set(src)
142132
case Indent:
143133
dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1)
144-
dst.Flags.Clear(jsonflags.SpaceAfterComma | jsonflags.SpaceAfterColon)
145134
dst.Indent = string(src)
146135
case IndentPrefix:
147136
dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1)
148-
dst.Flags.Clear(jsonflags.SpaceAfterComma | jsonflags.SpaceAfterColon)
149137
dst.IndentPrefix = string(src)
150138
case ByteLimit:
151139
dst.Flags.Set(jsonflags.ByteLimit | 1)

internal/jsonopts/options_test.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,22 @@ func TestJoin(t *testing.T) {
6969
CoderValues: CoderValues{Indent: "\t"},
7070
},
7171
}, {
72-
in: &DefaultOptionsV1, want: &DefaultOptionsV1, // v1 fully replaces before
72+
in: &DefaultOptionsV1, want: func() *Struct {
73+
v1 := DefaultOptionsV1
74+
v1.Flags.Set(jsonflags.Indent | 1)
75+
v1.Flags.Set(jsonflags.Multiline | 0)
76+
v1.Indent = "\t"
77+
return &v1
78+
}(), // v1 fully replaces before (except for whitespace related flags)
7379
}, {
74-
in: &DefaultOptionsV2, want: &DefaultOptionsV2}, // v2 fully replaces before
75-
}
80+
in: &DefaultOptionsV2, want: func() *Struct {
81+
v2 := DefaultOptionsV2
82+
v2.Flags.Set(jsonflags.Indent | 1)
83+
v2.Flags.Set(jsonflags.Multiline | 0)
84+
v2.Indent = "\t"
85+
return &v2
86+
}(), // v2 fully replaces before (except for whitespace related flags)
87+
}}
7688
got := new(Struct)
7789
for i, tt := range tests {
7890
got.Join(tt.in)

jsontext/encode.go

+22-16
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,17 @@ func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) {
116116
}
117117
e.Struct = jsonopts.Struct{}
118118
e.Struct.Join(opts...)
119-
if e.Flags.Get(jsonflags.Multiline) && !e.Flags.Has(jsonflags.Indent) {
120-
e.Indent = "\t"
119+
if e.Flags.Get(jsonflags.Multiline) {
120+
if !e.Flags.Has(jsonflags.SpaceAfterColon) {
121+
e.Flags.Set(jsonflags.SpaceAfterColon | 1)
122+
}
123+
if !e.Flags.Has(jsonflags.SpaceAfterComma) {
124+
e.Flags.Set(jsonflags.SpaceAfterComma | 0)
125+
}
126+
if !e.Flags.Has(jsonflags.Indent) {
127+
e.Flags.Set(jsonflags.Indent | 1)
128+
e.Indent = "\t"
129+
}
121130
}
122131
}
123132

@@ -274,7 +283,6 @@ func (e *encoderState) UnwriteEmptyObjectMember(prevName *string) bool {
274283
b = b[:len(b)-n]
275284
b = jsonwire.TrimSuffixWhitespace(b)
276285
b = jsonwire.TrimSuffixByte(b, ':')
277-
b = jsonwire.TrimSuffixWhitespace(b)
278286
b = jsonwire.TrimSuffixString(b)
279287
b = jsonwire.TrimSuffixWhitespace(b)
280288
b = jsonwire.TrimSuffixByte(b, ',')
@@ -582,15 +590,15 @@ func (e *encoderState) WriteValue(v Value) error {
582590
// appendWhitespace appends whitespace that immediately precedes the next token.
583591
func (e *encoderState) appendWhitespace(b []byte, next Kind) []byte {
584592
if delim := e.Tokens.needDelim(next); delim == ':' {
585-
if e.Flags.Get(jsonflags.Multiline | jsonflags.SpaceAfterColon) {
586-
return append(b, ' ')
593+
if e.Flags.Get(jsonflags.SpaceAfterColon) {
594+
b = append(b, ' ')
587595
}
588596
} else {
589-
if e.Flags.Get(jsonflags.Multiline) {
590-
return e.AppendIndent(b, e.Tokens.NeedIndent(next))
591-
}
592597
if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) {
593-
return append(b, ' ')
598+
b = append(b, ' ')
599+
}
600+
if e.Flags.Get(jsonflags.Multiline) {
601+
b = e.AppendIndent(b, e.Tokens.NeedIndent(next))
594602
}
595603
}
596604
return b
@@ -726,7 +734,7 @@ func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte,
726734
}
727735
dst = append(dst, ':')
728736
n += len(":")
729-
if e.Flags.Get(jsonflags.Multiline | jsonflags.SpaceAfterColon) {
737+
if e.Flags.Get(jsonflags.SpaceAfterColon) {
730738
dst = append(dst, ' ')
731739
}
732740

@@ -748,10 +756,9 @@ func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte,
748756
}
749757
switch src[n] {
750758
case ',':
759+
dst = append(dst, ',')
751760
if e.Flags.Get(jsonflags.SpaceAfterComma) {
752-
dst = append(dst, ',', ' ')
753-
} else {
754-
dst = append(dst, ',')
761+
dst = append(dst, ' ')
755762
}
756763
n += len(",")
757764
continue
@@ -819,10 +826,9 @@ func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte,
819826
}
820827
switch src[n] {
821828
case ',':
829+
dst = append(dst, ',')
822830
if e.Flags.Get(jsonflags.SpaceAfterComma) {
823-
dst = append(dst, ',', ' ')
824-
} else {
825-
dst = append(dst, ',')
831+
dst = append(dst, ' ')
826832
}
827833
n += len(",")
828834
continue

jsontext/encode_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,36 @@ var encoderErrorTestdata = []struct {
394394
{ArrayEnd, nil, ""},
395395
},
396396
wantOut: "[]\n",
397+
}, {
398+
name: jsontest.Name("Format/Object/SpaceAfterColon"),
399+
opts: []Options{SpaceAfterColon(true)},
400+
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
401+
wantOut: "{\"fizz\": \"buzz\",\"wizz\": \"wuzz\"}\n",
402+
}, {
403+
name: jsontest.Name("Format/Object/SpaceAfterComma"),
404+
opts: []Options{SpaceAfterComma(true)},
405+
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
406+
wantOut: "{\"fizz\":\"buzz\", \"wizz\":\"wuzz\"}\n",
407+
}, {
408+
name: jsontest.Name("Format/Object/SpaceAfterColonAndComma"),
409+
opts: []Options{SpaceAfterColon(true), SpaceAfterComma(true)},
410+
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
411+
wantOut: "{\"fizz\": \"buzz\", \"wizz\": \"wuzz\"}\n",
412+
}, {
413+
name: jsontest.Name("Format/Object/NoSpaceAfterColon+SpaceAfterComma+Multiline"),
414+
opts: []Options{SpaceAfterColon(false), SpaceAfterComma(true), Multiline(true)},
415+
calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}},
416+
wantOut: "{\n\t\"fizz\":\"buzz\", \n\t\"wizz\":\"wuzz\"\n}\n",
417+
}, {
418+
name: jsontest.Name("Format/Array/SpaceAfterComma"),
419+
opts: []Options{SpaceAfterComma(true)},
420+
calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}},
421+
wantOut: "[\"fizz\", \"buzz\"]\n",
422+
}, {
423+
name: jsontest.Name("Format/Array/NoSpaceAfterComma+Multiline"),
424+
opts: []Options{SpaceAfterComma(false), Multiline(true)},
425+
calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}},
426+
wantOut: "[\n\t\"fizz\",\n\t\"buzz\"\n]\n",
397427
}}
398428

399429
// TestEncoderErrors test that Encoder errors occur when we expect and

0 commit comments

Comments
 (0)