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