github.com/goccy/go-json@v0.10.3-0.20240509105655-5e2ae3f23c1d/internal/encoder/compact.go (about) 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 start = cursor + 3 217 cursor += 2 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 }