4
4
5
5
package json
6
6
7
- import (
8
- "bytes"
9
- )
7
+ import "bytes"
8
+
9
+ // TODO(https://go.dev/issue/53685): Use bytes.Buffer.AvailableBuffer instead.
10
+ func availableBuffer (b * bytes.Buffer ) []byte {
11
+ return b .Bytes ()[b .Len ():]
12
+ }
10
13
11
14
// Compact appends to dst the JSON-encoded src with
12
15
// insignificant space characters elided.
13
16
func Compact (dst * bytes.Buffer , src []byte ) error {
14
- return compact (dst , src , false )
17
+ dst .Grow (len (src ))
18
+ b := availableBuffer (dst )
19
+ b , err := appendCompact (b , src , false )
20
+ dst .Write (b )
21
+ return err
15
22
}
16
23
17
- func compact (dst * bytes. Buffer , src []byte , escape bool ) error {
18
- origLen := dst . Len ( )
24
+ func appendCompact (dst , src []byte , escape bool ) ([] byte , error ) {
25
+ origLen := len ( dst )
19
26
scan := newScanner ()
20
27
defer freeScanner (scan )
21
28
start := 0
22
29
for i , c := range src {
23
30
if escape && (c == '<' || c == '>' || c == '&' ) {
24
- if start < i {
25
- dst .Write (src [start :i ])
26
- }
27
- dst .WriteString (`\u00` )
28
- dst .WriteByte (hex [c >> 4 ])
29
- dst .WriteByte (hex [c & 0xF ])
31
+ dst = append (dst , src [start :i ]... )
32
+ dst = append (dst , '\\' , 'u' , '0' , '0' , hex [c >> 4 ], hex [c & 0xF ])
30
33
start = i + 1
31
34
}
32
35
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
33
36
if escape && c == 0xE2 && i + 2 < len (src ) && src [i + 1 ] == 0x80 && src [i + 2 ]&^1 == 0xA8 {
34
- if start < i {
35
- dst .Write (src [start :i ])
36
- }
37
- dst .WriteString (`\u202` )
38
- dst .WriteByte (hex [src [i + 2 ]& 0xF ])
39
- start = i + 3
37
+ dst = append (dst , src [start :i ]... )
38
+ dst = append (dst , '\\' , 'u' , '2' , '0' , '2' , hex [src [i + 2 ]& 0xF ])
39
+ start = i + len ("\u2029 " )
40
40
}
41
41
v := scan .step (scan , c )
42
42
if v >= scanSkipSpace {
43
43
if v == scanError {
44
44
break
45
45
}
46
- if start < i {
47
- dst .Write (src [start :i ])
48
- }
46
+ dst = append (dst , src [start :i ]... )
49
47
start = i + 1
50
48
}
51
49
}
52
50
if scan .eof () == scanError {
53
- dst .Truncate (origLen )
54
- return scan .err
55
- }
56
- if start < len (src ) {
57
- dst .Write (src [start :])
51
+ return dst [:origLen ], scan .err
58
52
}
59
- return nil
53
+ dst = append (dst , src [start :]... )
54
+ return dst , nil
60
55
}
61
56
62
- func newline (dst * bytes. Buffer , prefix , indent string , depth int ) {
63
- dst . WriteByte ( '\n' )
64
- dst . WriteString ( prefix )
57
+ func appendNewline (dst [] byte , prefix , indent string , depth int ) [] byte {
58
+ dst = append ( dst , '\n' )
59
+ dst = append ( dst , prefix ... )
65
60
for i := 0 ; i < depth ; i ++ {
66
- dst . WriteString ( indent )
61
+ dst = append ( dst , indent ... )
67
62
}
63
+ return dst
68
64
}
69
65
66
+ // indentGrowthFactor specifies the growth factor of indenting JSON input.
67
+ // Empirically, the growth factor was measured to be between 1.4x to 1.8x
68
+ // for some set of compacted JSON with the indent being a single tab.
69
+ // Specify a growth factor slightly larger than what is observed
70
+ // to reduce probability of allocation in appendIndent.
71
+ // A factor no higher than 2 ensures that wasted space never exceeds 50%.
72
+ const indentGrowthFactor = 2
73
+
70
74
// Indent appends to dst an indented form of the JSON-encoded src.
71
75
// Each element in a JSON object or array begins on a new,
72
76
// indented line beginning with prefix followed by one or more
@@ -79,7 +83,15 @@ func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
79
83
// For example, if src has no trailing spaces, neither will dst;
80
84
// if src ends in a trailing newline, so will dst.
81
85
func Indent (dst * bytes.Buffer , src []byte , prefix , indent string ) error {
82
- origLen := dst .Len ()
86
+ dst .Grow (indentGrowthFactor * len (src ))
87
+ b := availableBuffer (dst )
88
+ b , err := appendIndent (b , src , prefix , indent )
89
+ dst .Write (b )
90
+ return err
91
+ }
92
+
93
+ func appendIndent (dst , src []byte , prefix , indent string ) ([]byte , error ) {
94
+ origLen := len (dst )
83
95
scan := newScanner ()
84
96
defer freeScanner (scan )
85
97
needIndent := false
@@ -96,13 +108,13 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
96
108
if needIndent && v != scanEndObject && v != scanEndArray {
97
109
needIndent = false
98
110
depth ++
99
- newline (dst , prefix , indent , depth )
111
+ dst = appendNewline (dst , prefix , indent , depth )
100
112
}
101
113
102
114
// Emit semantically uninteresting bytes
103
115
// (in particular, punctuation in strings) unmodified.
104
116
if v == scanContinue {
105
- dst . WriteByte ( c )
117
+ dst = append ( dst , c )
106
118
continue
107
119
}
108
120
@@ -111,33 +123,27 @@ func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
111
123
case '{' , '[' :
112
124
// delay indent so that empty object and array are formatted as {} and [].
113
125
needIndent = true
114
- dst .WriteByte (c )
115
-
126
+ dst = append (dst , c )
116
127
case ',' :
117
- dst .WriteByte (c )
118
- newline (dst , prefix , indent , depth )
119
-
128
+ dst = append (dst , c )
129
+ dst = appendNewline (dst , prefix , indent , depth )
120
130
case ':' :
121
- dst .WriteByte (c )
122
- dst .WriteByte (' ' )
123
-
131
+ dst = append (dst , c , ' ' )
124
132
case '}' , ']' :
125
133
if needIndent {
126
134
// suppress indent in empty object/array
127
135
needIndent = false
128
136
} else {
129
137
depth --
130
- newline (dst , prefix , indent , depth )
138
+ dst = appendNewline (dst , prefix , indent , depth )
131
139
}
132
- dst .WriteByte (c )
133
-
140
+ dst = append (dst , c )
134
141
default :
135
- dst . WriteByte ( c )
142
+ dst = append ( dst , c )
136
143
}
137
144
}
138
145
if scan .eof () == scanError {
139
- dst .Truncate (origLen )
140
- return scan .err
146
+ return dst [:origLen ], scan .err
141
147
}
142
- return nil
148
+ return dst , nil
143
149
}
0 commit comments