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