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