File: zj.go 1 /* 2 The MIT License (MIT) 3 4 Copyright © 2020-2025 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the “Software”), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 Single-file source-code for zj. 27 28 To compile a smaller-sized command-line app, you can use the `go` command as 29 follows: 30 31 go build -ldflags "-s -w" -trimpath zj.go 32 */ 33 34 package main 35 36 import ( 37 "bufio" 38 "encoding/json" 39 "errors" 40 "fmt" 41 "io" 42 "os" 43 "sort" 44 "strconv" 45 "strings" 46 ) 47 48 // errNoMoreOutput is a generic dummy output-error, which is meant to be 49 // ultimately ignored, being just an excuse to quit the app immediately 50 // and successfully 51 var errNoMoreOutput = errors.New(`no more output`) 52 53 func main() { 54 if err := run(os.Args[1:]); err != nil && err != errNoMoreOutput { 55 os.Stderr.WriteString("\x1b[31m") 56 os.Stderr.WriteString(err.Error()) 57 os.Stderr.WriteString("\x1b[0m\n") 58 os.Exit(1) 59 } 60 } 61 62 func run(keys []string) error { 63 data, err := load(os.Stdin) 64 if err != nil { 65 return err 66 } 67 68 data, err = zoom(data, keys) 69 if err != nil { 70 return err 71 } 72 73 return output(os.Stdout, data) 74 } 75 76 // object is a map with ordered keys 77 type object struct { 78 keys []string 79 kv map[string]any 80 } 81 82 func load(r io.Reader) (any, error) { 83 dec := json.NewDecoder(r) 84 // avoid parsing numbers, so unusually-long numbers are kept verbatim, 85 // even if JSON parsers aren't required to guarantee such input-fidelity 86 // for numbers 87 dec.UseNumber() 88 89 t, err := dec.Token() 90 if err == io.EOF { 91 return nil, errors.New(`input has no JSON values`) 92 } 93 94 data, err := loadToken(dec, t) 95 if err != nil { 96 return nil, err 97 } 98 99 _, err = dec.Token() 100 if err == io.EOF { 101 // input is over, so it's a success 102 return data, nil 103 } 104 105 if err == nil { 106 // a successful `read` is a failure, as it means there are 107 // trailing JSON tokens 108 return data, errors.New(`unexpected trailing data`) 109 } 110 111 // any other error, perhaps some invalid-JSON-syntax-type error 112 return data, err 113 } 114 115 func loadToken(dec *json.Decoder, t json.Token) (any, error) { 116 switch t := t.(type) { 117 case json.Delim: 118 switch t { 119 case json.Delim('['): 120 return loadArray(dec) 121 case json.Delim('{'): 122 return loadObject(dec) 123 default: 124 return nil, errors.New(`unsupported JSON syntax`) 125 } 126 127 case nil: 128 return nil, nil 129 130 case bool: 131 return t, nil 132 133 case json.Number: 134 return t, nil 135 136 case string: 137 return t, nil 138 139 default: 140 // return fmt.Errorf(`unsupported token type %T`, t) 141 return t, errors.New(`invalid JSON token`) 142 } 143 } 144 145 func loadArray(dec *json.Decoder) ([]any, error) { 146 var data []any 147 148 for { 149 t, err := dec.Token() 150 if err == io.EOF { 151 return data, nil 152 } 153 154 if err != nil { 155 return data, err 156 } 157 158 if t == json.Delim(']') { 159 return data, nil 160 } 161 162 v, err := loadToken(dec, t) 163 if err != nil { 164 return data, err 165 } 166 data = append(data, v) 167 } 168 } 169 170 func loadObject(dec *json.Decoder) (object, error) { 171 var data object 172 data.kv = make(map[string]any) 173 174 for { 175 t, err := dec.Token() 176 if err == io.EOF { 177 return data, nil 178 } 179 180 if err != nil { 181 return data, err 182 } 183 184 if t == json.Delim('}') { 185 return data, nil 186 } 187 188 k, ok := t.(string) 189 if !ok { 190 return data, errors.New(`expected a string for a key-value pair`) 191 } 192 193 t, err = dec.Token() 194 if err == io.EOF { 195 return data, errors.New(`expected a value for a key-value pair`) 196 } 197 198 v, err := loadToken(dec, t) 199 if err != nil { 200 return data, err 201 } 202 203 if _, ok := data.kv[k]; !ok { 204 data.keys = append(data.keys, k) 205 } 206 data.kv[k] = v 207 } 208 } 209 210 var fallbackAliases = map[string]string{ 211 `.i`: `.info`, 212 `.k`: `.keys`, 213 `.l`: `.length`, 214 `.len`: `.length`, 215 `.t`: `.type`, 216 `.set`: `.unique`, 217 `.u`: `.unique`, 218 `.uniq`: `.unique`, 219 } 220 221 var fallbacks = map[string]func(v any, k string, rest []string) (any, error){ 222 `+`: pick, 223 `-`: drop, 224 `.keys`: keys, 225 `.info`: info, 226 `.length`: length, 227 `.tally`: tally, 228 `.type`: kind, 229 `.unique`: unique, 230 } 231 232 func info(data any, k string, rest []string) (any, error) { 233 switch data := data.(type) { 234 case nil: 235 return `null`, nil 236 237 case bool: 238 if data { 239 return `true (boolean)`, nil 240 } 241 return `false (boolean)`, nil 242 243 case json.Number: 244 return data.String() + ` (number)`, nil 245 246 case string: 247 c := 0 248 for range data { 249 c++ 250 } 251 return `string (` + strconv.Itoa(c) + ` runes)`, nil 252 253 case []any: 254 return `array (` + strconv.Itoa(len(data)) + ` items)`, nil 255 256 case object: 257 return `object (` + strconv.Itoa(len(data.keys)) + ` items)`, nil 258 259 default: 260 return ``, errors.New(`unsupported type`) 261 } 262 } 263 264 func keys(data any, k string, rest []string) (any, error) { 265 switch data := data.(type) { 266 default: 267 return nil, nil 268 269 case string: 270 c := 0 271 for range data { 272 c++ 273 } 274 return c, nil 275 276 case []any: 277 keys := make([]any, 0, len(data)) 278 for i := range data { 279 keys = append(keys, i) 280 } 281 return keys, nil 282 283 case object: 284 keys := make([]any, 0, len(data.keys)) 285 for _, k := range data.keys { 286 keys = append(keys, k) 287 } 288 return keys, nil 289 } 290 } 291 292 func length(data any, k string, rest []string) (any, error) { 293 switch data := data.(type) { 294 default: 295 return nil, nil 296 297 case string: 298 c := 0 299 for range data { 300 c++ 301 } 302 return c, nil 303 304 case []any: 305 return len(data), nil 306 307 case object: 308 return len(data.keys), nil 309 } 310 } 311 312 func kind(data any, k string, rest []string) (any, error) { 313 switch data.(type) { 314 case nil: 315 return `null`, nil 316 case bool: 317 return `boolean`, nil 318 case int, json.Number: 319 return `number`, nil 320 case string: 321 return `string`, nil 322 case []any: 323 return `array`, nil 324 case object: 325 return `object`, nil 326 default: 327 // return ``, errors.New(`unsupported type`) 328 return fmt.Sprintf(`%T`, data), nil 329 } 330 } 331 332 func tally(data any, k string, rest []string) (any, error) { 333 switch data := data.(type) { 334 default: 335 return nil, nil 336 case []any: 337 return tallyArray(data), nil 338 } 339 } 340 341 type tallySortable struct { 342 keys []string 343 tally map[string]int 344 } 345 346 func (ts tallySortable) Len() int { 347 return len(ts.keys) 348 } 349 350 func (ts tallySortable) Less(i, j int) bool { 351 return ts.tally[ts.keys[i]] > ts.tally[ts.keys[j]] 352 } 353 354 func (ts tallySortable) Swap(i, j int) { 355 ts.keys[i], ts.keys[j] = ts.keys[j], ts.keys[i] 356 } 357 358 func tallyArray(data []any) any { 359 var keys []string 360 tally := make(map[string]int) 361 362 for _, v := range data { 363 d, err := json.Marshal(v) 364 if err != nil { 365 continue 366 } 367 tag := string(d) 368 369 if _, ok := tally[tag]; !ok { 370 keys = append(keys, tag) 371 } 372 tally[tag]++ 373 } 374 375 var sorted object 376 sorted.kv = make(map[string]any) 377 sort.Sort(tallySortable{keys: keys, tally: tally}) 378 for _, tag := range keys { 379 sorted.keys = append(sorted.keys, tag) 380 sorted.kv[tag] = tally[tag] 381 } 382 return sorted 383 } 384 385 func unique(data any, k string, rest []string) (any, error) { 386 switch data := data.(type) { 387 default: 388 return data, nil 389 case []any: 390 return uniqueArray(data), nil 391 } 392 } 393 394 func uniqueArray(data []any) any { 395 var unique []any 396 got := make(map[string]struct{}) 397 398 for _, v := range data { 399 d, err := json.Marshal(v) 400 if err != nil { 401 continue 402 } 403 tag := string(d) 404 405 if _, ok := got[tag]; ok { 406 continue 407 } 408 409 unique = append(unique, v) 410 got[tag] = struct{}{} 411 } 412 413 return unique 414 } 415 416 func pick(data any, k string, rest []string) (any, error) { 417 switch data := data.(type) { 418 case []any: 419 return pickArray(data, rest) 420 case object: 421 return pickObject(data, rest), nil 422 default: 423 return nil, nil 424 } 425 } 426 427 func drop(data any, k string, rest []string) (any, error) { 428 switch data := data.(type) { 429 case []any: 430 return dropArray(data, rest) 431 case object: 432 return dropObject(data, uniqueStrings(rest)), nil 433 default: 434 return nil, nil 435 } 436 } 437 438 func dealias(s string, aliases map[string]string) string { 439 if alias, ok := aliases[s]; ok { 440 return alias 441 } 442 return s 443 } 444 445 func zoom(data any, keys []string) (any, error) { 446 for i, k := range keys { 447 switch v := data.(type) { 448 case []any: 449 if i, ok := matchIndex(k, len(v)); ok { 450 data = v[i] 451 continue 452 } 453 454 if i, j, ok := matchSlice(k, len(v)); ok { 455 if len(v) > 0 { 456 data = v[i:j] 457 } 458 continue 459 } 460 461 if f, ok := fallbacks[dealias(k, fallbackAliases)]; ok { 462 v, err := f(data, k, keys[i+1:]) 463 if err != nil { 464 return v, err 465 } 466 data = v 467 continue 468 } 469 470 // data = nil 471 return data, errors.New(`arrays have no properties`) 472 473 case object: 474 if key, ok := matchKey(v, k); ok { 475 data = v.kv[key] 476 continue 477 } 478 479 if i, j, ok := matchSlice(k, len(v.keys)); ok { 480 if len(v.keys) > 0 { 481 v.keys = v.keys[i:j] 482 data = v 483 } 484 continue 485 } 486 487 if f, ok := fallbacks[dealias(k, fallbackAliases)]; ok { 488 v, err := f(data, k, keys[i+1:]) 489 if err != nil { 490 return v, err 491 } 492 data = v 493 continue 494 } 495 496 data = nil 497 498 default: 499 // return nil, errors.New(`can only zoom on arrays/objects`) 500 data = nil 501 } 502 } 503 504 return data, nil 505 } 506 507 func matchIndex(k string, length int) (int, bool) { 508 if i, err := strconv.Atoi(k); err == nil { 509 if i < 0 { 510 i += length 511 } 512 513 if 0 <= i && i < length { 514 return i, true 515 } 516 } 517 518 return -1, false 519 } 520 521 func matchKey(data object, k string) (string, bool) { 522 // try exact key-match 523 if _, ok := data.kv[k]; ok { 524 return k, true 525 } 526 527 // try a case-insensitive key-match 528 for _, dk := range data.keys { 529 if strings.EqualFold(k, dk) { 530 return dk, true 531 } 532 } 533 534 // try number-indexing 535 if i, err := strconv.Atoi(k); err == nil { 536 if i < 0 { 537 i += len(data.keys) 538 } 539 540 if 0 <= i && i < len(data.keys) { 541 return data.keys[i], true 542 } else { 543 return ``, false 544 } 545 } 546 547 return ``, false 548 } 549 550 func matchSlice(k string, length int) (int, int, bool) { 551 k = strings.TrimSpace(k) 552 colon := strings.IndexByte(k, ':') 553 554 // when there's no colon in it, key isn't a slice for sure 555 if colon < 0 { 556 return 0, 0, false 557 } 558 559 // can't slice an empty container 560 if length == 0 { 561 return 0, 0, false 562 } 563 564 // when given `:` 565 if len(k) == 1 { 566 return 0, length, true 567 } 568 569 // when slice starts with `:`, it implies starting from 0 570 if colon == 0 { 571 if end, ok := matchIndex(k[colon+1:], length); ok { 572 return 0, end, true 573 } 574 return 0, 0, false 575 } 576 577 // when slice ends with `:`, it implies until the end 578 if colon == len(k)-1 { 579 if start, ok := matchIndex(k[:colon], length); ok { 580 return start, length, true 581 } 582 return 0, 0, false 583 } 584 585 start, ok := matchIndex(k[:colon], length) 586 if !ok { 587 return 0, 0, false 588 } 589 590 end, ok := matchIndex(k[colon+1:], length) 591 if !ok { 592 return 0, 0, false 593 } 594 595 // backward/reversed slices aren't supported 596 if start > end { 597 return 0, 0, false 598 } 599 600 // restrict slices to valid ranges 601 if start < 0 { 602 start = 0 603 } 604 if end > length { 605 end = length 606 } 607 return start, end, true 608 } 609 610 func pickArray(data []any, keys []string) ([]any, error) { 611 var res []any 612 for _, v := range data { 613 if o, ok := v.(object); ok { 614 res = append(res, pickObject(o, keys)) 615 } 616 } 617 return res, nil 618 } 619 620 func dropArray(data []any, keys []string) ([]any, error) { 621 var res []any 622 avoid := uniqueStrings(keys) 623 for _, v := range data { 624 if o, ok := v.(object); ok { 625 res = append(res, dropObject(o, avoid)) 626 } 627 } 628 return res, nil 629 } 630 631 func pickObject(data object, keys []string) object { 632 var o object 633 o.kv = make(map[string]any) 634 for _, k := range keys { 635 if k, ok := matchKey(data, k); ok { 636 o.keys = append(o.keys, k) 637 o.kv[k] = data.kv[k] 638 } 639 } 640 return o 641 } 642 643 func dropObject(data object, avoid map[string]struct{}) object { 644 var o object 645 o.kv = make(map[string]any) 646 for _, k := range data.keys { 647 if _, ok := avoid[k]; !ok { 648 o.keys = append(o.keys, k) 649 o.kv[k] = data.kv[k] 650 } 651 } 652 return o 653 } 654 655 func uniqueStrings(values []string) map[string]struct{} { 656 unique := make(map[string]struct{}) 657 for _, v := range values { 658 unique[v] = struct{}{} 659 } 660 return unique 661 } 662 663 // escapedStringBytes helps func outputString treat all string bytes quickly 664 // and correctly, using their officially-supported JSON escape sequences 665 // 666 // https://www.rfc-editor.org/rfc/rfc8259#section-7 667 var escapedStringBytes = [256][]byte{ 668 {'\\', 'u', '0', '0', '0', '0'}, {'\\', 'u', '0', '0', '0', '1'}, 669 {'\\', 'u', '0', '0', '0', '2'}, {'\\', 'u', '0', '0', '0', '3'}, 670 {'\\', 'u', '0', '0', '0', '4'}, {'\\', 'u', '0', '0', '0', '5'}, 671 {'\\', 'u', '0', '0', '0', '6'}, {'\\', 'u', '0', '0', '0', '7'}, 672 {'\\', 'b'}, {'\\', 't'}, 673 {'\\', 'n'}, {'\\', 'u', '0', '0', '0', 'b'}, 674 {'\\', 'f'}, {'\\', 'r'}, 675 {'\\', 'u', '0', '0', '0', 'e'}, {'\\', 'u', '0', '0', '0', 'f'}, 676 {'\\', 'u', '0', '0', '1', '0'}, {'\\', 'u', '0', '0', '1', '1'}, 677 {'\\', 'u', '0', '0', '1', '2'}, {'\\', 'u', '0', '0', '1', '3'}, 678 {'\\', 'u', '0', '0', '1', '4'}, {'\\', 'u', '0', '0', '1', '5'}, 679 {'\\', 'u', '0', '0', '1', '6'}, {'\\', 'u', '0', '0', '1', '7'}, 680 {'\\', 'u', '0', '0', '1', '8'}, {'\\', 'u', '0', '0', '1', '9'}, 681 {'\\', 'u', '0', '0', '1', 'a'}, {'\\', 'u', '0', '0', '1', 'b'}, 682 {'\\', 'u', '0', '0', '1', 'c'}, {'\\', 'u', '0', '0', '1', 'd'}, 683 {'\\', 'u', '0', '0', '1', 'e'}, {'\\', 'u', '0', '0', '1', 'f'}, 684 {32}, {33}, {'\\', '"'}, {35}, {36}, {37}, {38}, {39}, 685 {40}, {41}, {42}, {43}, {44}, {45}, {46}, {47}, 686 {48}, {49}, {50}, {51}, {52}, {53}, {54}, {55}, 687 {56}, {57}, {58}, {59}, {60}, {61}, {62}, {63}, 688 {64}, {65}, {66}, {67}, {68}, {69}, {70}, {71}, 689 {72}, {73}, {74}, {75}, {76}, {77}, {78}, {79}, 690 {80}, {81}, {82}, {83}, {84}, {85}, {86}, {87}, 691 {88}, {89}, {90}, {91}, {'\\', '\\'}, {93}, {94}, {95}, 692 {96}, {97}, {98}, {99}, {100}, {101}, {102}, {103}, 693 {104}, {105}, {106}, {107}, {108}, {109}, {110}, {111}, 694 {112}, {113}, {114}, {115}, {116}, {117}, {118}, {119}, 695 {120}, {121}, {122}, {123}, {124}, {125}, {126}, {127}, 696 {128}, {129}, {130}, {131}, {132}, {133}, {134}, {135}, 697 {136}, {137}, {138}, {139}, {140}, {141}, {142}, {143}, 698 {144}, {145}, {146}, {147}, {148}, {149}, {150}, {151}, 699 {152}, {153}, {154}, {155}, {156}, {157}, {158}, {159}, 700 {160}, {161}, {162}, {163}, {164}, {165}, {166}, {167}, 701 {168}, {169}, {170}, {171}, {172}, {173}, {174}, {175}, 702 {176}, {177}, {178}, {179}, {180}, {181}, {182}, {183}, 703 {184}, {185}, {186}, {187}, {188}, {189}, {190}, {191}, 704 {192}, {193}, {194}, {195}, {196}, {197}, {198}, {199}, 705 {200}, {201}, {202}, {203}, {204}, {205}, {206}, {207}, 706 {208}, {209}, {210}, {211}, {212}, {213}, {214}, {215}, 707 {216}, {217}, {218}, {219}, {220}, {221}, {222}, {223}, 708 {224}, {225}, {226}, {227}, {228}, {229}, {230}, {231}, 709 {232}, {233}, {234}, {235}, {236}, {237}, {238}, {239}, 710 {240}, {241}, {242}, {243}, {244}, {245}, {246}, {247}, 711 {248}, {249}, {250}, {251}, {252}, {253}, {254}, {255}, 712 } 713 714 // writeSpaces does what it says, minimizing calls to write-like funcs 715 func writeSpaces(w *bufio.Writer, n int) { 716 const spaces = ` ` 717 if n < 1 { 718 return 719 } 720 721 for n >= len(spaces) { 722 w.WriteString(spaces) 723 n -= len(spaces) 724 } 725 w.WriteString(spaces[:n]) 726 } 727 728 func output(w io.Writer, data any) error { 729 bw := bufio.NewWriter(w) 730 defer bw.Flush() 731 732 if err := outputValue(bw, data, 0, 0); err != nil { 733 return err 734 } 735 bw.WriteRune('\n') 736 return nil 737 } 738 739 func outputValue(w *bufio.Writer, data any, pre, level int) error { 740 switch data := data.(type) { 741 case nil: 742 w.WriteString(`null`) 743 return nil 744 745 case bool: 746 if data { 747 w.WriteString(`true`) 748 } else { 749 w.WriteString(`false`) 750 } 751 return nil 752 753 case int: 754 var buf [24]byte 755 w.Write(strconv.AppendInt(buf[:0], int64(data), 10)) 756 return nil 757 758 case json.Number: 759 writeSpaces(w, 2*pre) 760 w.WriteString(data.String()) 761 return nil 762 763 case string: 764 return outputString(w, data, pre) 765 766 case []any: 767 return outputArray(w, data, pre, level+1) 768 769 case object: 770 return outputObject(w, data, pre, level+1) 771 772 default: 773 return errors.New(`unexpected value type`) 774 } 775 } 776 777 func outputArray(w *bufio.Writer, data []any, pre, level int) error { 778 writeSpaces(w, 2*pre) 779 w.WriteByte('[') 780 781 for i, v := range data { 782 if i > 0 { 783 _, err := w.WriteString(",\n") 784 if err != nil { 785 return errNoMoreOutput 786 } 787 } else { 788 w.WriteByte('\n') 789 } 790 791 if err := outputValue(w, v, level, level); err != nil { 792 return err 793 } 794 } 795 796 endComposite(len(data), w, level-1, ']') 797 return nil 798 } 799 800 func outputObject(w *bufio.Writer, data object, pre, level int) error { 801 writeSpaces(w, 2*pre) 802 w.WriteByte('{') 803 804 for i, k := range data.keys { 805 if i > 0 { 806 _, err := w.WriteString(",\n") 807 if err != nil { 808 return errNoMoreOutput 809 } 810 } else { 811 w.WriteByte('\n') 812 } 813 814 if err := outputString(w, k, level); err != nil { 815 return err 816 } 817 818 w.WriteString(": ") 819 820 if err := outputValue(w, data.kv[k], 0, level); err != nil { 821 return err 822 } 823 } 824 825 endComposite(len(data.keys), w, pre, '}') 826 return nil 827 } 828 829 func outputString(w *bufio.Writer, s string, level int) error { 830 writeSpaces(w, 2*level) 831 w.WriteByte('"') 832 for i := range s { 833 w.Write(escapedStringBytes[s[i]]) 834 } 835 w.WriteByte('"') 836 return nil 837 } 838 839 // endComposite handles common closing logic which is used twice both in each 840 // of func handleArray and func handleObject 841 func endComposite(i int, w *bufio.Writer, pre int, end byte) { 842 if i > 0 { 843 w.WriteByte('\n') 844 writeSpaces(w, 2*pre) 845 } 846 w.WriteByte(end) 847 }