1 package encoder
2
3 import (
4 "bytes"
5 "fmt"
6 "strconv"
7 "unsafe"
8
9 "github.com/goccy/go-json/internal/errors"
10 )
11
12 var (
13 isWhiteSpace = [256]bool{
14 ' ': true,
15 '\n': true,
16 '\t': true,
17 '\r': true,
18 }
19 isHTMLEscapeChar = [256]bool{
20 '<': true,
21 '>': true,
22 '&': true,
23 }
24 nul = byte('\000')
25 )
26
27 func Compact(buf *bytes.Buffer, src []byte, escape bool) error {
28 if len(src) == 0 {
29 return errors.ErrUnexpectedEndOfJSON("", 0)
30 }
31 buf.Grow(len(src))
32 dst := buf.Bytes()
33
34 ctx := TakeRuntimeContext()
35 ctxBuf := ctx.Buf[:0]
36 ctxBuf = append(append(ctxBuf, src...), nul)
37 ctx.Buf = ctxBuf
38
39 if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil {
40 ReleaseRuntimeContext(ctx)
41 return err
42 }
43 ReleaseRuntimeContext(ctx)
44 return nil
45 }
46
47 func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error {
48 dst, err := compact(dst, src, escape)
49 if err != nil {
50 return err
51 }
52 if _, err := buf.Write(dst); err != nil {
53 return err
54 }
55 return nil
56 }
57
58 func compact(dst, src []byte, escape bool) ([]byte, error) {
59 buf, cursor, err := compactValue(dst, src, 0, escape)
60 if err != nil {
61 return nil, err
62 }
63 if err := validateEndBuf(src, cursor); err != nil {
64 return nil, err
65 }
66 return buf, nil
67 }
68
69 func validateEndBuf(src []byte, cursor int64) error {
70 for {
71 switch src[cursor] {
72 case ' ', '\t', '\n', '\r':
73 cursor++
74 continue
75 case nul:
76 return nil
77 }
78 return errors.ErrSyntax(
79 fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]),
80 cursor+1,
81 )
82 }
83 }
84
85 func skipWhiteSpace(buf []byte, cursor int64) int64 {
86 LOOP:
87 if isWhiteSpace[buf[cursor]] {
88 cursor++
89 goto LOOP
90 }
91 return cursor
92 }
93
94 func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
95 for {
96 switch src[cursor] {
97 case ' ', '\t', '\n', '\r':
98 cursor++
99 continue
100 case '{':
101 return compactObject(dst, src, cursor, escape)
102 case '}':
103 return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor)
104 case '[':
105 return compactArray(dst, src, cursor, escape)
106 case ']':
107 return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor)
108 case '"':
109 return compactString(dst, src, cursor, escape)
110 case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
111 return compactNumber(dst, src, cursor)
112 case 't':
113 return compactTrue(dst, src, cursor)
114 case 'f':
115 return compactFalse(dst, src, cursor)
116 case 'n':
117 return compactNull(dst, src, cursor)
118 default:
119 return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor)
120 }
121 }
122 }
123
124 func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
125 if src[cursor] == '{' {
126 dst = append(dst, '{')
127 } else {
128 return nil, 0, errors.ErrExpected("expected { character for object value", cursor)
129 }
130 cursor = skipWhiteSpace(src, cursor+1)
131 if src[cursor] == '}' {
132 dst = append(dst, '}')
133 return dst, cursor + 1, nil
134 }
135 var err error
136 for {
137 cursor = skipWhiteSpace(src, cursor)
138 dst, cursor, err = compactString(dst, src, cursor, escape)
139 if err != nil {
140 return nil, 0, err
141 }
142 cursor = skipWhiteSpace(src, cursor)
143 if src[cursor] != ':' {
144 return nil, 0, errors.ErrExpected("colon after object key", cursor)
145 }
146 dst = append(dst, ':')
147 dst, cursor, err = compactValue(dst, src, cursor+1, escape)
148 if err != nil {
149 return nil, 0, err
150 }
151 cursor = skipWhiteSpace(src, cursor)
152 switch src[cursor] {
153 case '}':
154 dst = append(dst, '}')
155 cursor++
156 return dst, cursor, nil
157 case ',':
158 dst = append(dst, ',')
159 default:
160 return nil, 0, errors.ErrExpected("comma after object value", cursor)
161 }
162 cursor++
163 }
164 }
165
166 func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
167 if src[cursor] == '[' {
168 dst = append(dst, '[')
169 } else {
170 return nil, 0, errors.ErrExpected("expected [ character for array value", cursor)
171 }
172 cursor = skipWhiteSpace(src, cursor+1)
173 if src[cursor] == ']' {
174 dst = append(dst, ']')
175 return dst, cursor + 1, nil
176 }
177 var err error
178 for {
179 dst, cursor, err = compactValue(dst, src, cursor, escape)
180 if err != nil {
181 return nil, 0, err
182 }
183 cursor = skipWhiteSpace(src, cursor)
184 switch src[cursor] {
185 case ']':
186 dst = append(dst, ']')
187 cursor++
188 return dst, cursor, nil
189 case ',':
190 dst = append(dst, ',')
191 default:
192 return nil, 0, errors.ErrExpected("comma after array value", cursor)
193 }
194 cursor++
195 }
196 }
197
198 func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
199 if src[cursor] != '"' {
200 return nil, 0, errors.ErrInvalidCharacter(src[cursor], "string", cursor)
201 }
202 start := cursor
203 for {
204 cursor++
205 c := src[cursor]
206 if escape {
207 if isHTMLEscapeChar[c] {
208 dst = append(dst, src[start:cursor]...)
209 dst = append(dst, `\u00`...)
210 dst = append(dst, hex[c>>4], hex[c&0xF])
211 start = cursor + 1
212 } else if c == 0xE2 && cursor+2 < int64(len(src)) && src[cursor+1] == 0x80 && src[cursor+2]&^1 == 0xA8 {
213 dst = append(dst, src[start:cursor]...)
214 dst = append(dst, `\u202`...)
215 dst = append(dst, hex[src[cursor+2]&0xF])
216 cursor += 2
217 start = cursor + 3
218 }
219 }
220 switch c {
221 case '\\':
222 cursor++
223 if src[cursor] == nul {
224 return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
225 }
226 case '"':
227 cursor++
228 return append(dst, src[start:cursor]...), cursor, nil
229 case nul:
230 return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
231 }
232 }
233 }
234
235 func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) {
236 start := cursor
237 for {
238 cursor++
239 if floatTable[src[cursor]] {
240 continue
241 }
242 break
243 }
244 num := src[start:cursor]
245 if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&num)), 64); err != nil {
246 return nil, 0, err
247 }
248 dst = append(dst, num...)
249 return dst, cursor, nil
250 }
251
252 func compactTrue(dst, src []byte, cursor int64) ([]byte, int64, error) {
253 if cursor+3 >= int64(len(src)) {
254 return nil, 0, errors.ErrUnexpectedEndOfJSON("true", cursor)
255 }
256 if !bytes.Equal(src[cursor:cursor+4], []byte(`true`)) {
257 return nil, 0, errors.ErrInvalidCharacter(src[cursor], "true", cursor)
258 }
259 dst = append(dst, "true"...)
260 cursor += 4
261 return dst, cursor, nil
262 }
263
264 func compactFalse(dst, src []byte, cursor int64) ([]byte, int64, error) {
265 if cursor+4 >= int64(len(src)) {
266 return nil, 0, errors.ErrUnexpectedEndOfJSON("false", cursor)
267 }
268 if !bytes.Equal(src[cursor:cursor+5], []byte(`false`)) {
269 return nil, 0, errors.ErrInvalidCharacter(src[cursor], "false", cursor)
270 }
271 dst = append(dst, "false"...)
272 cursor += 5
273 return dst, cursor, nil
274 }
275
276 func compactNull(dst, src []byte, cursor int64) ([]byte, int64, error) {
277 if cursor+3 >= int64(len(src)) {
278 return nil, 0, errors.ErrUnexpectedEndOfJSON("null", cursor)
279 }
280 if !bytes.Equal(src[cursor:cursor+4], []byte(`null`)) {
281 return nil, 0, errors.ErrInvalidCharacter(src[cursor], "null", cursor)
282 }
283 dst = append(dst, "null"...)
284 cursor += 4
285 return dst, cursor, nil
286 }
287
View as plain text