github.com/fufuok/utils@v1.0.10/xjson/pretty/pretty.go (about) 1 package pretty 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "sort" 7 "strconv" 8 ) 9 10 // Options is Pretty options 11 type Options struct { 12 // Width is an max column width for single line arrays 13 // Default is 80 14 Width int 15 // Prefix is a prefix for all lines 16 // Default is an empty string 17 Prefix string 18 // Indent is the nested indentation 19 // Default is two spaces 20 Indent string 21 // SortKeys will sort the keys alphabetically 22 // Default is false 23 SortKeys bool 24 } 25 26 // DefaultOptions is the default options for pretty formats. 27 var DefaultOptions = &Options{Width: 80, Prefix: "", Indent: " ", SortKeys: false} 28 29 // Pretty converts the input json into a more human readable format where each 30 // element is on it's own line with clear indentation. 31 func Pretty(json []byte) []byte { return PrettyOptions(json, nil) } 32 33 // PrettyOptions is like Pretty but with customized options. 34 func PrettyOptions(json []byte, opts *Options) []byte { 35 if opts == nil { 36 opts = DefaultOptions 37 } 38 buf := make([]byte, 0, len(json)) 39 if len(opts.Prefix) != 0 { 40 buf = append(buf, opts.Prefix...) 41 } 42 buf, _, _, _ = appendPrettyAny(buf, json, 0, true, 43 opts.Width, opts.Prefix, opts.Indent, opts.SortKeys, 44 0, 0, -1) 45 if len(buf) > 0 { 46 buf = append(buf, '\n') 47 } 48 return buf 49 } 50 51 // Ugly removes insignificant space characters from the input json byte slice 52 // and returns the compacted result. 53 func Ugly(json []byte) []byte { 54 buf := make([]byte, 0, len(json)) 55 return ugly(buf, json) 56 } 57 58 // UglyInPlace removes insignificant space characters from the input json 59 // byte slice and returns the compacted result. This method reuses the 60 // input json buffer to avoid allocations. Do not use the original bytes 61 // slice upon return. 62 func UglyInPlace(json []byte) []byte { return ugly(json, json) } 63 64 func ugly(dst, src []byte) []byte { 65 dst = dst[:0] 66 for i := 0; i < len(src); i++ { 67 if src[i] > ' ' { 68 dst = append(dst, src[i]) 69 if src[i] == '"' { 70 for i = i + 1; i < len(src); i++ { 71 dst = append(dst, src[i]) 72 if src[i] == '"' { 73 j := i - 1 74 for ; ; j-- { 75 if src[j] != '\\' { 76 break 77 } 78 } 79 if (j-i)%2 != 0 { 80 break 81 } 82 } 83 } 84 } 85 } 86 } 87 return dst 88 } 89 90 func isNaNOrInf(src []byte) bool { 91 return src[0] == 'i' || // Inf 92 src[0] == 'I' || // inf 93 src[0] == '+' || // +Inf 94 src[0] == 'N' || // Nan 95 (src[0] == 'n' && len(src) > 1 && src[1] != 'u') // nan 96 } 97 98 func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) { 99 for ; i < len(json); i++ { 100 if json[i] <= ' ' { 101 continue 102 } 103 if json[i] == '"' { 104 return appendPrettyString(buf, json, i, nl) 105 } 106 107 if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(json[i:]) { 108 return appendPrettyNumber(buf, json, i, nl) 109 } 110 if json[i] == '{' { 111 return appendPrettyObject(buf, json, i, '{', '}', pretty, width, prefix, indent, sortkeys, tabs, nl, max) 112 } 113 if json[i] == '[' { 114 return appendPrettyObject(buf, json, i, '[', ']', pretty, width, prefix, indent, sortkeys, tabs, nl, max) 115 } 116 switch json[i] { 117 case 't': 118 return append(buf, 't', 'r', 'u', 'e'), i + 4, nl, true 119 case 'f': 120 return append(buf, 'f', 'a', 'l', 's', 'e'), i + 5, nl, true 121 case 'n': 122 return append(buf, 'n', 'u', 'l', 'l'), i + 4, nl, true 123 } 124 } 125 return buf, i, nl, true 126 } 127 128 type pair struct { 129 kstart, kend int 130 vstart, vend int 131 } 132 133 type byKeyVal struct { 134 sorted bool 135 json []byte 136 buf []byte 137 pairs []pair 138 } 139 140 func (arr *byKeyVal) Len() int { 141 return len(arr.pairs) 142 } 143 144 func (arr *byKeyVal) Less(i, j int) bool { 145 if arr.isLess(i, j, byKey) { 146 return true 147 } 148 if arr.isLess(j, i, byKey) { 149 return false 150 } 151 return arr.isLess(i, j, byVal) 152 } 153 154 func (arr *byKeyVal) Swap(i, j int) { 155 arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i] 156 arr.sorted = true 157 } 158 159 type byKind int 160 161 const ( 162 byKey byKind = 0 163 byVal byKind = 1 164 ) 165 166 type jtype int 167 168 const ( 169 jnull jtype = iota 170 jfalse 171 jnumber 172 jstring 173 jtrue 174 jjson 175 ) 176 177 func getjtype(v []byte) jtype { 178 if len(v) == 0 { 179 return jnull 180 } 181 switch v[0] { 182 case '"': 183 return jstring 184 case 'f': 185 return jfalse 186 case 't': 187 return jtrue 188 case 'n': 189 return jnull 190 case '[', '{': 191 return jjson 192 default: 193 return jnumber 194 } 195 } 196 197 func (arr *byKeyVal) isLess(i, j int, kind byKind) bool { 198 k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend] 199 k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend] 200 var v1, v2 []byte 201 if kind == byKey { 202 v1 = k1 203 v2 = k2 204 } else { 205 v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend]) 206 v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend]) 207 if len(v1) >= len(k1)+1 { 208 v1 = bytes.TrimSpace(v1[len(k1)+1:]) 209 } 210 if len(v2) >= len(k2)+1 { 211 v2 = bytes.TrimSpace(v2[len(k2)+1:]) 212 } 213 } 214 t1 := getjtype(v1) 215 t2 := getjtype(v2) 216 if t1 < t2 { 217 return true 218 } 219 if t1 > t2 { 220 return false 221 } 222 if t1 == jstring { 223 s1 := parsestr(v1) 224 s2 := parsestr(v2) 225 return string(s1) < string(s2) 226 } 227 if t1 == jnumber { 228 n1, _ := strconv.ParseFloat(string(v1), 64) 229 n2, _ := strconv.ParseFloat(string(v2), 64) 230 return n1 < n2 231 } 232 return string(v1) < string(v2) 233 } 234 235 func parsestr(s []byte) []byte { 236 for i := 1; i < len(s); i++ { 237 if s[i] == '\\' { 238 var str string 239 json.Unmarshal(s, &str) 240 return []byte(str) 241 } 242 if s[i] == '"' { 243 return s[1:i] 244 } 245 } 246 return nil 247 } 248 249 func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) { 250 var ok bool 251 if width > 0 { 252 if pretty && open == '[' && max == -1 { 253 // here we try to create a single line array 254 max := width - (len(buf) - nl) 255 if max > 3 { 256 s1, s2 := len(buf), i 257 buf, i, _, ok = appendPrettyObject(buf, json, i, '[', ']', false, width, prefix, "", sortkeys, 0, 0, max) 258 if ok && len(buf)-s1 <= max { 259 return buf, i, nl, true 260 } 261 buf = buf[:s1] 262 i = s2 263 } 264 } else if max != -1 && open == '{' { 265 return buf, i, nl, false 266 } 267 } 268 buf = append(buf, open) 269 i++ 270 var pairs []pair 271 if open == '{' && sortkeys { 272 pairs = make([]pair, 0, 8) 273 } 274 var n int 275 for ; i < len(json); i++ { 276 if json[i] <= ' ' { 277 continue 278 } 279 if json[i] == close { 280 if pretty { 281 if open == '{' && sortkeys { 282 buf = sortPairs(json, buf, pairs) 283 } 284 if n > 0 { 285 nl = len(buf) 286 if buf[nl-1] == ' ' { 287 buf[nl-1] = '\n' 288 } else { 289 buf = append(buf, '\n') 290 } 291 } 292 if buf[len(buf)-1] != open { 293 buf = appendTabs(buf, prefix, indent, tabs) 294 } 295 } 296 buf = append(buf, close) 297 return buf, i + 1, nl, open != '{' 298 } 299 if open == '[' || json[i] == '"' { 300 if n > 0 { 301 buf = append(buf, ',') 302 if width != -1 && open == '[' { 303 buf = append(buf, ' ') 304 } 305 } 306 var p pair 307 if pretty { 308 nl = len(buf) 309 if buf[nl-1] == ' ' { 310 buf[nl-1] = '\n' 311 } else { 312 buf = append(buf, '\n') 313 } 314 if open == '{' && sortkeys { 315 p.kstart = i 316 p.vstart = len(buf) 317 } 318 buf = appendTabs(buf, prefix, indent, tabs+1) 319 } 320 if open == '{' { 321 buf, i, nl, _ = appendPrettyString(buf, json, i, nl) 322 if sortkeys { 323 p.kend = i 324 } 325 buf = append(buf, ':') 326 if pretty { 327 buf = append(buf, ' ') 328 } 329 } 330 buf, i, nl, ok = appendPrettyAny(buf, json, i, pretty, width, prefix, indent, sortkeys, tabs+1, nl, max) 331 if max != -1 && !ok { 332 return buf, i, nl, false 333 } 334 if pretty && open == '{' && sortkeys { 335 p.vend = len(buf) 336 if p.kstart > p.kend || p.vstart > p.vend { 337 // bad data. disable sorting 338 sortkeys = false 339 } else { 340 pairs = append(pairs, p) 341 } 342 } 343 i-- 344 n++ 345 } 346 } 347 return buf, i, nl, open != '{' 348 } 349 350 func sortPairs(json, buf []byte, pairs []pair) []byte { 351 if len(pairs) == 0 { 352 return buf 353 } 354 vstart := pairs[0].vstart 355 vend := pairs[len(pairs)-1].vend 356 arr := byKeyVal{false, json, buf, pairs} 357 sort.Stable(&arr) 358 if !arr.sorted { 359 return buf 360 } 361 nbuf := make([]byte, 0, vend-vstart) 362 for i, p := range pairs { 363 nbuf = append(nbuf, buf[p.vstart:p.vend]...) 364 if i < len(pairs)-1 { 365 nbuf = append(nbuf, ',') 366 nbuf = append(nbuf, '\n') 367 } 368 } 369 return append(buf[:vstart], nbuf...) 370 } 371 372 func appendPrettyString(buf, json []byte, i, nl int) ([]byte, int, int, bool) { 373 s := i 374 i++ 375 for ; i < len(json); i++ { 376 if json[i] == '"' { 377 var sc int 378 for j := i - 1; j > s; j-- { 379 if json[j] == '\\' { 380 sc++ 381 } else { 382 break 383 } 384 } 385 if sc%2 == 1 { 386 continue 387 } 388 i++ 389 break 390 } 391 } 392 return append(buf, json[s:i]...), i, nl, true 393 } 394 395 func appendPrettyNumber(buf, json []byte, i, nl int) ([]byte, int, int, bool) { 396 s := i 397 i++ 398 for ; i < len(json); i++ { 399 if json[i] <= ' ' || json[i] == ',' || json[i] == ':' || json[i] == ']' || json[i] == '}' { 400 break 401 } 402 } 403 return append(buf, json[s:i]...), i, nl, true 404 } 405 406 func appendTabs(buf []byte, prefix, indent string, tabs int) []byte { 407 if len(prefix) != 0 { 408 buf = append(buf, prefix...) 409 } 410 if len(indent) == 2 && indent[0] == ' ' && indent[1] == ' ' { 411 for i := 0; i < tabs; i++ { 412 buf = append(buf, ' ', ' ') 413 } 414 } else { 415 for i := 0; i < tabs; i++ { 416 buf = append(buf, indent...) 417 } 418 } 419 return buf 420 } 421 422 // Style is the color style 423 type Style struct { 424 Key, String, Number [2]string 425 True, False, Null [2]string 426 Escape [2]string 427 Append func(dst []byte, c byte) []byte 428 } 429 430 func hexp(p byte) byte { 431 switch { 432 case p < 10: 433 return p + '0' 434 default: 435 return (p - 10) + 'a' 436 } 437 } 438 439 // TerminalStyle is for terminals 440 var TerminalStyle *Style 441 442 func init() { 443 TerminalStyle = &Style{ 444 Key: [2]string{"\x1B[94m", "\x1B[0m"}, 445 String: [2]string{"\x1B[92m", "\x1B[0m"}, 446 Number: [2]string{"\x1B[93m", "\x1B[0m"}, 447 True: [2]string{"\x1B[96m", "\x1B[0m"}, 448 False: [2]string{"\x1B[96m", "\x1B[0m"}, 449 Null: [2]string{"\x1B[91m", "\x1B[0m"}, 450 Escape: [2]string{"\x1B[35m", "\x1B[0m"}, 451 Append: func(dst []byte, c byte) []byte { 452 if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') { 453 dst = append(dst, "\\u00"...) 454 dst = append(dst, hexp((c>>4)&0xF)) 455 return append(dst, hexp((c)&0xF)) 456 } 457 return append(dst, c) 458 }, 459 } 460 } 461 462 // Color will colorize the json. The style parma is used for customizing 463 // the colors. Passing nil to the style param will use the default 464 // TerminalStyle. 465 func Color(src []byte, style *Style) []byte { 466 if style == nil { 467 style = TerminalStyle 468 } 469 apnd := style.Append 470 if apnd == nil { 471 apnd = func(dst []byte, c byte) []byte { 472 return append(dst, c) 473 } 474 } 475 type stackt struct { 476 kind byte 477 key bool 478 } 479 var dst []byte 480 var stack []stackt 481 for i := 0; i < len(src); i++ { 482 if src[i] == '"' { 483 key := len(stack) > 0 && stack[len(stack)-1].key 484 if key { 485 dst = append(dst, style.Key[0]...) 486 } else { 487 dst = append(dst, style.String[0]...) 488 } 489 dst = apnd(dst, '"') 490 esc := false 491 uesc := 0 492 for i = i + 1; i < len(src); i++ { 493 if src[i] == '\\' { 494 if key { 495 dst = append(dst, style.Key[1]...) 496 } else { 497 dst = append(dst, style.String[1]...) 498 } 499 dst = append(dst, style.Escape[0]...) 500 dst = apnd(dst, src[i]) 501 esc = true 502 if i+1 < len(src) && src[i+1] == 'u' { 503 uesc = 5 504 } else { 505 uesc = 1 506 } 507 } else if esc { 508 dst = apnd(dst, src[i]) 509 if uesc == 1 { 510 esc = false 511 dst = append(dst, style.Escape[1]...) 512 if key { 513 dst = append(dst, style.Key[0]...) 514 } else { 515 dst = append(dst, style.String[0]...) 516 } 517 } else { 518 uesc-- 519 } 520 } else { 521 dst = apnd(dst, src[i]) 522 } 523 if src[i] == '"' { 524 j := i - 1 525 for ; ; j-- { 526 if src[j] != '\\' { 527 break 528 } 529 } 530 if (j-i)%2 != 0 { 531 break 532 } 533 } 534 } 535 if esc { 536 dst = append(dst, style.Escape[1]...) 537 } else if key { 538 dst = append(dst, style.Key[1]...) 539 } else { 540 dst = append(dst, style.String[1]...) 541 } 542 } else if src[i] == '{' || src[i] == '[' { 543 stack = append(stack, stackt{src[i], src[i] == '{'}) 544 dst = apnd(dst, src[i]) 545 } else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 { 546 stack = stack[:len(stack)-1] 547 dst = apnd(dst, src[i]) 548 } else if (src[i] == ':' || src[i] == ',') && len(stack) > 0 && stack[len(stack)-1].kind == '{' { 549 stack[len(stack)-1].key = !stack[len(stack)-1].key 550 dst = apnd(dst, src[i]) 551 } else { 552 var kind byte 553 if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) { 554 kind = '0' 555 dst = append(dst, style.Number[0]...) 556 } else if src[i] == 't' { 557 kind = 't' 558 dst = append(dst, style.True[0]...) 559 } else if src[i] == 'f' { 560 kind = 'f' 561 dst = append(dst, style.False[0]...) 562 } else if src[i] == 'n' { 563 kind = 'n' 564 dst = append(dst, style.Null[0]...) 565 } else { 566 dst = apnd(dst, src[i]) 567 } 568 if kind != 0 { 569 for ; i < len(src); i++ { 570 if src[i] <= ' ' || src[i] == ',' || src[i] == ':' || src[i] == ']' || src[i] == '}' { 571 i-- 572 break 573 } 574 dst = apnd(dst, src[i]) 575 } 576 if kind == '0' { 577 dst = append(dst, style.Number[1]...) 578 } else if kind == 't' { 579 dst = append(dst, style.True[1]...) 580 } else if kind == 'f' { 581 dst = append(dst, style.False[1]...) 582 } else if kind == 'n' { 583 dst = append(dst, style.Null[1]...) 584 } 585 } 586 } 587 } 588 return dst 589 } 590 591 // Spec strips out comments and trailing commas and convert the input to a 592 // valid JSON per the official spec: https://tools.ietf.org/html/rfc8259 593 // 594 // The resulting JSON will always be the same length as the input and it will 595 // include all of the same line breaks at matching offsets. This is to ensure 596 // the result can be later processed by a external parser and that that 597 // parser will report messages or errors with the correct offsets. 598 func Spec(src []byte) []byte { 599 return spec(src, nil) 600 } 601 602 // SpecInPlace is the same as Spec, but this method reuses the input json 603 // buffer to avoid allocations. Do not use the original bytes slice upon return. 604 func SpecInPlace(src []byte) []byte { 605 return spec(src, src) 606 } 607 608 func spec(src, dst []byte) []byte { 609 dst = dst[:0] 610 for i := 0; i < len(src); i++ { 611 if src[i] == '/' { 612 if i < len(src)-1 { 613 if src[i+1] == '/' { 614 dst = append(dst, ' ', ' ') 615 i += 2 616 for ; i < len(src); i++ { 617 if src[i] == '\n' { 618 dst = append(dst, '\n') 619 break 620 } else if src[i] == '\t' || src[i] == '\r' { 621 dst = append(dst, src[i]) 622 } else { 623 dst = append(dst, ' ') 624 } 625 } 626 continue 627 } 628 if src[i+1] == '*' { 629 dst = append(dst, ' ', ' ') 630 i += 2 631 for ; i < len(src)-1; i++ { 632 if src[i] == '*' && src[i+1] == '/' { 633 dst = append(dst, ' ', ' ') 634 i++ 635 break 636 } else if src[i] == '\n' || src[i] == '\t' || 637 src[i] == '\r' { 638 dst = append(dst, src[i]) 639 } else { 640 dst = append(dst, ' ') 641 } 642 } 643 continue 644 } 645 } 646 } 647 dst = append(dst, src[i]) 648 if src[i] == '"' { 649 for i = i + 1; i < len(src); i++ { 650 dst = append(dst, src[i]) 651 if src[i] == '"' { 652 j := i - 1 653 for ; ; j-- { 654 if src[j] != '\\' { 655 break 656 } 657 } 658 if (j-i)%2 != 0 { 659 break 660 } 661 } 662 } 663 } else if src[i] == '}' || src[i] == ']' { 664 for j := len(dst) - 2; j >= 0; j-- { 665 if dst[j] <= ' ' { 666 continue 667 } 668 if dst[j] == ',' { 669 dst[j] = ' ' 670 } 671 break 672 } 673 } 674 } 675 return dst 676 }