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 }