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 }