File: ./bytes.go
   1 package json
   2 
   3 var isIdent = [256]bool{
   4     '0': true, '1': true, '2': true, '3': true, '4': true,
   5     '5': true, '6': true, '7': true, '8': true, '9': true,
   6 
   7     'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true,
   8     'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true,
   9     'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true,
  10     'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true,
  11     'Y': true, 'Z': true,
  12 
  13     '_': true,
  14 
  15     'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true,
  16     'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true,
  17     'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true,
  18     's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true,
  19     'y': true, 'z': true,
  20 }
  21 
  22 func isIdentSymbol(b byte) bool {
  23     return isIdent[b]
  24 }
  25 
  26 var isWhitespace = [256]bool{
  27     '\t': true,
  28     ' ':  true,
  29     '\n': true,
  30     '\r': true,
  31 }
  32 
  33 // skipWhitespace ignores leading whitespace in the buffer given
  34 func skipWhitespace(buf []byte) []byte {
  35     for len(buf) > 0 {
  36         if isWhitespace[buf[0]] {
  37             buf = buf[1:]
  38             continue
  39         }
  40         return buf
  41     }
  42     return buf
  43 }

     File: ./bytes_test.go
   1 package json
   2 
   3 import "testing"
   4 
   5 func BenchmarkIsIdentSymbol(b *testing.B) {
   6     b.Run(`is-ident-1`, func(b *testing.B) {
   7         b.ReportAllocs()
   8         b.ResetTimer()
   9 
  10         for i := 0; i < b.N; i++ {
  11             for v := byte(0); i <= 255; i++ {
  12                 _ = isIdentSymbol1(v)
  13             }
  14         }
  15     })
  16 
  17     b.Run(`is-ident-2`, func(b *testing.B) {
  18         b.ReportAllocs()
  19         b.ResetTimer()
  20 
  21         for i := 0; i < b.N; i++ {
  22             for v := byte(0); i <= 255; i++ {
  23                 _ = isIdentSymbol2(v)
  24             }
  25         }
  26     })
  27 }
  28 
  29 func isIdentSymbol1(b byte) bool {
  30     return isIdent[b]
  31 }
  32 
  33 func isIdentSymbol2(b byte) bool {
  34     return b == '_' || isLetter(b) || ('0' <= b && b <= '9')
  35 }
  36 
  37 func isLetter(b byte) bool {
  38     return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z')
  39 }
  40 
  41 func BenchmarkIsWhitespace(b *testing.B) {
  42     b.Run(`is-ws-1`, func(b *testing.B) {
  43         b.ReportAllocs()
  44         b.ResetTimer()
  45 
  46         for i := 0; i < b.N; i++ {
  47             for v := byte(0); i <= 255; i++ {
  48                 _ = isWhitespace1(v)
  49             }
  50         }
  51     })
  52 
  53     b.Run(`is-ws-2`, func(b *testing.B) {
  54         b.ReportAllocs()
  55         b.ResetTimer()
  56 
  57         for i := 0; i < b.N; i++ {
  58             for v := byte(0); i <= 255; i++ {
  59                 _ = isWhitespace2(v)
  60             }
  61         }
  62     })
  63 }
  64 
  65 func isWhitespace1(b byte) bool {
  66     return b == ' ' || b == '\t' || b == '\n' || b == '\r'
  67 }
  68 
  69 func isWhitespace2(b byte) bool {
  70     return isWhitespace[b]
  71 }

     File: ./readers.go
   1 package json
   2 
   3 import (
   4     "bufio"
   5     "bytes"
   6     "errors"
   7     "fmt"
   8     "io"
   9     "math"
  10     "strconv"
  11     "strings"
  12     "unicode"
  13 )
  14 
  15 var (
  16     errComment       = errors.New(`strict JSON doesn't allow comments`)
  17     errTrailingComma = errors.New(`strict JSON doesn't allow trailing commas`)
  18     errUnderscoreNum = errors.New(`strict JSON doesn't allow underscores in numbers`)
  19 )
  20 
  21 const (
  22     // a decoder's string-builder minimum capacity
  23     sbmincap = 64
  24 
  25     // the preallocated size in a decoder's chunk
  26     chunkmaxsize = 128 * 1024
  27 )
  28 
  29 // Decoder implements a JSON decoder which happens to be noticeably faster
  30 // than the one in the go stdlib, even when the latter is used in streaming
  31 // mode.
  32 //
  33 // Also, unlike the stdlib, this decoder supports non-strict JSON by default:
  34 // this allows comments and trailing commas in arrays and objects. Strict
  35 // parsing is enabled via the boolean option of the same name.
  36 //
  37 // Independent of parsing-strictness is also the option to refuse repeating
  38 // keys in the same object, resulting in an error instead. This option is off
  39 // by default.
  40 //
  41 // Custom parsing of strings is available by setting func ParseString: this
  42 // callback is optional, and allows you to handle your custom types encoded
  43 // as strings. This callback only works on string values, and never handles
  44 // object keys.
  45 //
  46 // Finally, and again unlike the stdlib, this decoder allows remembering the
  47 // order object keys appear, using the custom object-maker func MakeObject.
  48 //
  49 // When func MakeObject is not set, the default behavior is to make a value of
  50 // type Object, which is defined in this package to represent an ordered map.
  51 type Decoder struct {
  52     // chunk is the current/latest fragment read from the data source
  53     chunk []byte
  54 
  55     // cur is the current slice of the latest chunk
  56     cur []byte
  57 
  58     // n is the number of bytes read so far
  59     n int64
  60 
  61     // sb is a string-builder used to decode both string and number literals
  62     sb strings.Builder
  63 
  64     // r is the data source: when nil, chunk-reading is disabled, and all
  65     // bytes are expected to be in `cur`, the current slice
  66     r io.Reader
  67 
  68     // ParseString allows you to handle strings representing custom types:
  69     // it's called on all string values, but never on object keys.
  70     ParseString func(s string) (any, error)
  71 
  72     // MakeObject allows you to create custom ordered-object types: it's
  73     // called as soon as a new object is finished parsing. You can even
  74     // ignore the (ordered) slice of keys, and just use the unordered map.
  75     MakeObject func(keys []string, kv map[string]any) any
  76 
  77     // Strict enforces strict JSON parsing, which forbids comments and
  78     // trailing commas in arrays and objects.
  79     Strict bool
  80 
  81     // EnforceUniqueKeys causes an error if a key is reused in the same
  82     // object: the JSON format allows repeated object keys, so this option
  83     // is kept separate from the strict-JSON one.
  84     EnforceUniqueKeys bool
  85 }
  86 
  87 // Decode parses the input from the reader given.
  88 func (dec *Decoder) Decode(r io.Reader) (any, error) {
  89     dec.r = r
  90     dec.n = 0
  91     dec.cur = nil
  92 
  93     if dec.chunk == nil {
  94         dec.chunk = make([]byte, chunkmaxsize)
  95     }
  96     if dec.sb.Cap() == 0 {
  97         dec.sb.Grow(sbmincap)
  98     }
  99 
 100     if dec.MakeObject == nil {
 101         dec.MakeObject = zipObject
 102     }
 103 
 104     v, err := dec.read()
 105     dec.r = nil // ensure the decoder isn't preventing gc of the reader
 106     return v, err
 107 }
 108 
 109 // DecodeBytes parses the input from the bytes given.
 110 func (dec *Decoder) DecodeBytes(b []byte) (any, error) {
 111     dec.r = nil // disable chunk-reading
 112     dec.n = 0
 113     dec.cur = b
 114 
 115     // there's no need to ensure `chunk` is allocated, as chunk-reading
 116     // is disabled, which means it's never used here
 117 
 118     if dec.sb.Cap() == 0 {
 119         dec.sb.Grow(sbmincap)
 120     }
 121 
 122     if dec.MakeObject == nil {
 123         dec.MakeObject = zipObject
 124     }
 125 
 126     v, err := dec.read()
 127     dec.cur = nil // ensure the decoder isn't preventing gc of the bytes
 128     return v, err
 129 }
 130 
 131 // read is the entry point to read/decode JSON
 132 func (dec *Decoder) read() (any, error) {
 133     // read JSON value, likely a composite one such as an array or object
 134     v, err := dec.readValue()
 135     if err == io.EOF {
 136         return v, nil
 137     }
 138     if err != nil {
 139         return v, err
 140     }
 141 
 142     // ignore any comment after the value
 143     b, err := dec.seekSyntax()
 144     if err == io.EOF {
 145         return v, nil
 146     }
 147     if err != nil {
 148         return v, err
 149     }
 150     if b == '/' {
 151         if dec.Strict {
 152             return v, errComment
 153         }
 154 
 155         err := dec.skipComment()
 156         if err == io.EOF {
 157             return v, nil
 158         }
 159         return v, err
 160     }
 161 
 162     // ignore whitespace before checking if there are trailing bytes left
 163     dec.cur = skipWhitespace(dec.cur)
 164     if len(dec.cur) > 0 {
 165         const fs = `unexpected trailing data %s`
 166         return v, fmt.Errorf(fs, dec.limitbuf())
 167     }
 168     return v, nil
 169 }
 170 
 171 // readChunk advances thru the input chunk/block-wise
 172 func (dec *Decoder) readChunk() error {
 173     // remember if this is the 1st chunk read, for later BOM-removal
 174     firstChunk := dec.n == 0
 175 
 176     // read data, unless chunk-reading is disabled: when disabled, all
 177     // bytes are supposed to have been preloaded in the current slice
 178     if dec.r != nil {
 179         n, err := dec.r.Read(dec.chunk)
 180         if err == io.EOF && n > 0 {
 181             err = nil
 182         }
 183         if err != nil {
 184             return err
 185         }
 186 
 187         dec.cur = dec.chunk[:n]
 188         dec.n += int64(n)
 189     }
 190 
 191     if firstChunk {
 192         // Give nice errors for all sorts of unsupported Unicode formats,
 193         // whose byte-order-marks are described in the webpage/section below.
 194         //
 195         // Q: Is a BOM used only in 16-bit Unicode text?
 196         // https://www.unicode.org/faq/utf_bom.html
 197 
 198         if bytes.HasPrefix(dec.cur, []byte{0x00, 0x00, 0xfe, 0xff}) {
 199             return errors.New(`UTF-32BE not supported, only UTF-8 is`)
 200         }
 201         if bytes.HasPrefix(dec.cur, []byte{0xff, 0xfe, 0x00, 0x00}) {
 202             return errors.New(`UTF-32LE not supported, only UTF-8 is`)
 203         }
 204         if bytes.HasPrefix(dec.cur, []byte{0xfe, 0xff}) {
 205             return errors.New(`UTF-16BE not supported, only UTF-8 is`)
 206         }
 207         if bytes.HasPrefix(dec.cur, []byte{0xff, 0xfe}) {
 208             return errors.New(`UTF-16LE not supported, only UTF-8 is`)
 209         }
 210 
 211         // ignore UTF-8 byte-order-mark, if present
 212         dec.cur = bytes.TrimPrefix(dec.cur, []byte{0xef, 0xbb, 0xbf})
 213     }
 214 
 215     return nil
 216 }
 217 
 218 // peek sees what's the next byte, without advancing thru the input
 219 func (dec *Decoder) peek() (byte, error) {
 220     if len(dec.cur) == 0 {
 221         err := dec.readChunk()
 222         if err != nil {
 223             return 0, err
 224         }
 225     }
 226 
 227     if len(dec.cur) > 0 {
 228         return dec.cur[0], nil
 229     }
 230     return 0, io.EOF
 231 }
 232 
 233 // skip advances thru the input 1 byte, unless current buffer is empty: this
 234 // is meant to be used after `peek` succeeds, which ensures buffer isn't empty
 235 func (dec *Decoder) skip() {
 236     if len(dec.cur) > 0 {
 237         dec.cur = dec.cur[1:]
 238     }
 239 }
 240 
 241 // next peeks at the current byte, and advances after it
 242 func (dec *Decoder) next() (byte, error) {
 243     b, err := dec.peek()
 244     if err != nil {
 245         return b, err
 246     }
 247     dec.skip()
 248     return b, nil
 249 }
 250 
 251 // limitbuf returns a length-limited string of the current point in the
 252 // stream: this is useful to give context to some errors
 253 func (dec *Decoder) limitbuf() string {
 254     const max = 50
 255     if len(dec.cur) > max {
 256         return string(dec.cur[:max])
 257     }
 258     return string(dec.cur)
 259 }
 260 
 261 // keyword expects to match the keyword given: this is used to match literal
 262 // values null, false, and true
 263 func (dec *Decoder) keyword(s string) error {
 264     kw := s
 265     for ; len(s) > 0; s = s[1:] {
 266         b, err := dec.next()
 267         if err != nil {
 268             return err
 269         }
 270 
 271         ch := s[0]
 272         if b != ch {
 273             const fs = `after %q, expected keyword %q`
 274             return fmt.Errorf(fs, kw[0], kw)
 275         }
 276     }
 277 
 278     b, err := dec.peek()
 279     if err == nil && isIdentSymbol(b) {
 280         const fs = `after %q, expected keyword %q`
 281         return fmt.Errorf(fs, kw[0], kw)
 282     }
 283     return nil
 284 }
 285 
 286 // seekSyntax skips thru whitespace (syntactic noise) to reach the next
 287 // non-whitespace byte; if already on a non-whitespace byte, it stays there
 288 func (dec *Decoder) seekSyntax() (byte, error) {
 289     for {
 290         b, err := dec.peek()
 291         if err != nil {
 292             return b, err
 293         }
 294 
 295         switch b {
 296         case ' ', '\t', '\n', '\r', '\f', '\v':
 297             dec.skip()
 298 
 299         case '/':
 300             if dec.Strict {
 301                 return 0, errComment
 302             }
 303 
 304             err := dec.skipComment()
 305             if err != nil {
 306                 return 0, err
 307             }
 308 
 309         default:
 310             return b, nil
 311         }
 312     }
 313 }
 314 
 315 // skipComment skips either type of comment, single-line or /* ... */
 316 func (dec *Decoder) skipComment() error {
 317     b, err := dec.peek()
 318     if err != nil {
 319         return err
 320     }
 321     if b != '/' {
 322         const fs = `expected start of comment (%s)`
 323         return fmt.Errorf(fs, dec.limitbuf())
 324     }
 325     dec.skip()
 326 
 327     b, err = dec.peek()
 328     if err != nil {
 329         return err
 330     }
 331 
 332     switch b {
 333     case '/':
 334         return dec.skipLine()
 335     case '*':
 336         dec.skip()
 337         return dec.skipDelimitedComment()
 338     default:
 339         const fs = `invalid comment syntax after / (%s)`
 340         return fmt.Errorf(fs, dec.limitbuf())
 341     }
 342 }
 343 
 344 // skipLine implements skipping/ignoring single-line comments
 345 func (dec *Decoder) skipLine() error {
 346     for {
 347         b, err := dec.peek()
 348         if err != nil {
 349             return err
 350         }
 351         dec.skip()
 352 
 353         if b == '\n' {
 354             return nil
 355         }
 356     }
 357 }
 358 
 359 // skipDelimitedComment implements skipping comments which start with /* and
 360 // which end with */
 361 func (dec *Decoder) skipDelimitedComment() error {
 362     var prev byte
 363     for {
 364         b, err := dec.peek()
 365         if err != nil {
 366             return err
 367         }
 368         dec.skip()
 369 
 370         if prev == '*' && b == '/' {
 371             return nil
 372         }
 373         prev = b
 374     }
 375 }
 376 
 377 // readValue handles reading/decoding any JSON value, starting from the
 378 // current point in the input stream
 379 func (dec *Decoder) readValue() (any, error) {
 380     b, err := dec.seekSyntax()
 381     if err != nil {
 382         return nil, err
 383     }
 384 
 385     switch b {
 386     case 'n':
 387         err := dec.keyword(`null`)
 388         if err != nil {
 389             return nil, err
 390         }
 391         return nil, nil
 392 
 393     case 'f':
 394         err := dec.keyword(`false`)
 395         if err != nil {
 396             return nil, err
 397         }
 398         return false, nil
 399 
 400     case 't':
 401         err := dec.keyword(`true`)
 402         if err != nil {
 403             return nil, err
 404         }
 405         return true, nil
 406 
 407     case 'I':
 408         if dec.Strict {
 409             // strict mode disables Infinity as a valid keyword: JSON doesn't
 410             // officially allow it, despite some JSON parsers accepting it
 411             return nil, fmt.Errorf(`invalid rune %c`, b)
 412         }
 413         return dec.readInf(+1)
 414 
 415     case 'N':
 416         if dec.Strict {
 417             // strict mode disables NaN as a valid keyword: JSON doesn't
 418             // officially allow it, despite some JSON parsers accepting it
 419             return nil, fmt.Errorf(`invalid rune %c`, b)
 420         }
 421         err := dec.keyword(`NaN`)
 422         if err != nil {
 423             return nil, err
 424         }
 425         return math.NaN(), nil
 426 
 427     case '+':
 428         dec.next()
 429         if dec.Strict {
 430             // strict mode disables Infinity as a valid keyword: JSON doesn't
 431             // officially allow it, despite some JSON parsers accepting it
 432             return nil, fmt.Errorf(`invalid rune %c`, b)
 433         }
 434         b, _ := dec.peek()
 435         if b == 'I' {
 436             return dec.readInf(+1)
 437         }
 438         return dec.readFloat64()
 439 
 440     case '-':
 441         dec.next()
 442         if dec.Strict {
 443             // strict mode disables Infinity as a valid keyword: JSON doesn't
 444             // officially allow it, despite some JSON parsers accepting it
 445             v, err := dec.readFloat64()
 446             return -v, err
 447         }
 448         b, _ := dec.peek()
 449         if b == 'I' {
 450             return dec.readInf(-1)
 451         }
 452         v, err := dec.readFloat64()
 453         return -v, err
 454 
 455     case '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
 456         return dec.readFloat64()
 457 
 458     case '"', '\'':
 459         s, err := dec.readString()
 460         if err != nil {
 461             return s, err
 462         }
 463         if dec.ParseString != nil {
 464             return dec.ParseString(s)
 465         }
 466         return s, err
 467 
 468     case '[':
 469         return dec.readArray()
 470 
 471     case '{':
 472         return dec.readObject()
 473 
 474     default:
 475         if b == '\'' && !dec.Strict {
 476             s, err := dec.readString()
 477             if err != nil {
 478                 return s, err
 479             }
 480             if dec.ParseString != nil {
 481                 return dec.ParseString(s)
 482             }
 483             return s, err
 484         }
 485         return nil, fmt.Errorf(`invalid rune %c`, b)
 486     }
 487 }
 488 
 489 func (dec *Decoder) readInf(sign int) (any, error) {
 490     err := dec.keyword(`Inf`)
 491     if err != nil {
 492         return nil, err
 493     }
 494     b, _ := dec.peek()
 495     if b != 'i' {
 496         return math.Inf(sign), nil
 497     }
 498     err = dec.keyword(`inity`)
 499     if err != nil {
 500         const msg = "after `Infi`, expected the full keyword `Infinity`"
 501         return nil, errors.New(msg)
 502     }
 503     return math.Inf(sign), nil
 504 }
 505 
 506 // readFloat64 handles reading/decoding a number, starting from the current
 507 // point in the input stream
 508 func (dec *Decoder) readFloat64() (float64, error) {
 509     dec.sb.Reset()
 510 
 511     for {
 512         b, err := dec.peek()
 513         if err == io.EOF {
 514             if b != 0 {
 515                 dec.sb.WriteByte(b)
 516             }
 517             return dec.parseFloat64()
 518         }
 519         if err != nil {
 520             return 0, err
 521         }
 522 
 523         digit := '0' <= b && b <= '9'
 524         if digit || b == '+' || b == '-' || b == '.' || b == 'e' || b == 'E' {
 525             dec.sb.WriteByte(b)
 526             dec.skip()
 527             continue
 528         }
 529 
 530         // underscores in numbers are only allowed in non-strict mode
 531         if b == '_' {
 532             if dec.Strict {
 533                 return 0, errUnderscoreNum
 534             }
 535             dec.sb.WriteByte(b)
 536             dec.skip()
 537             continue
 538         }
 539 
 540         return dec.parseFloat64()
 541     }
 542 }
 543 
 544 func (dec *Decoder) parseFloat64() (float64, error) {
 545     f, err := strconv.ParseFloat(dec.sb.String(), 64)
 546     if err != nil {
 547         return f, err
 548     }
 549 
 550     if dec.Strict && math.IsInf(f, 0) {
 551         // strict mode disables both infinities as allowed float64 values:
 552         // JSON doesn't officially allow it, despite some JSON parsers
 553         // accepting it
 554         const fs = `%f isn't allowed in strict JSON`
 555         return f, fmt.Errorf(fs, f)
 556     }
 557 
 558     return f, nil
 559 }
 560 
 561 // readString handles reading/decoding a literal string, starting from the
 562 // current point in the input stream
 563 func (dec *Decoder) readString() (string, error) {
 564     // ensure string literal starts correctly
 565     b, err := dec.seekSyntax()
 566     if err != nil {
 567         return ``, err
 568     }
 569 
 570     if b != '"' {
 571         if dec.Strict {
 572             const fs = "expected a \" to begin a string, but got %s instead"
 573             return ``, fmt.Errorf(fs, string(b))
 574         }
 575         if b != '\'' {
 576             const fs = "expected a \" or a ' to begin a string, but got %s instead"
 577             return ``, fmt.Errorf(fs, string(b))
 578         }
 579     }
 580 
 581     quote := b
 582     dec.skip()
 583 
 584     num := 0
 585     digits := 0
 586     escaped := false
 587     dec.sb.Reset()
 588 
 589     for {
 590         b, err := dec.next()
 591         if err == io.EOF {
 592             const fs = `string literal not closed: %q`
 593             err = fmt.Errorf(fs, dec.sb.String())
 594         }
 595         if err != nil {
 596             return dec.sb.String(), err
 597         }
 598 
 599         if escaped {
 600             escaped = false
 601             if b == 'u' {
 602                 num = 0
 603                 digits = 4
 604                 continue
 605             }
 606             dec.sb.WriteByte(escape(b))
 607             continue
 608         }
 609 
 610         if digits > 0 {
 611             d := 0
 612             if '0' <= b && b <= '9' {
 613                 d = int(b - '0')
 614             } else if 'A' <= b && b <= 'F' {
 615                 d = int(b-'A') + 10
 616             } else if 'a' <= b && b <= 'f' {
 617                 d = int(b-'a') + 10
 618             } else {
 619                 const fs = `invalid string-literal base-16 code: %s`
 620                 return dec.sb.String(), fmt.Errorf(fs, string(b))
 621             }
 622 
 623             num *= 16
 624             num += d
 625             digits--
 626 
 627             if digits == 0 {
 628                 dec.sb.WriteRune(rune(num))
 629             }
 630             continue
 631         }
 632 
 633         switch b {
 634         case '\\':
 635             escaped = true
 636 
 637         default:
 638             if b == quote {
 639                 if escaped {
 640                     dec.sb.WriteByte(quote)
 641                     escaped = false
 642                     continue
 643                 }
 644                 return dec.sb.String(), nil
 645             }
 646             dec.sb.WriteByte(b)
 647         }
 648     }
 649 }
 650 
 651 // readKey implements parsing a key-value pair's key: in strict-JSON mode,
 652 // it can only be a double-quoted string, while in non-strict mode it can be
 653 // either an unquoted identifier, a single-quoted string, or a double-quoted
 654 // string
 655 func (dec *Decoder) readKey(start byte) (string, error) {
 656     if dec.Strict {
 657         if start != '"' {
 658             const fs = "expected \" but got %s instead"
 659             return ``, fmt.Errorf(fs, string(start))
 660         }
 661         return dec.readString()
 662     }
 663 
 664     // handle non-strict-mode quoted string
 665     if start == '"' || start == '\'' {
 666         return dec.readString()
 667     }
 668 
 669     // handle non-strict-mode identifier / unquoted-key-string
 670     dec.sb.Reset()
 671 
 672     for {
 673         b, err := dec.peek()
 674         if err != nil {
 675             return dec.sb.String(), err
 676         }
 677 
 678         if !isIdentSymbol(b) {
 679             return dec.sb.String(), nil
 680         }
 681 
 682         dec.sb.WriteByte(b)
 683         dec.skip()
 684     }
 685 }
 686 
 687 // readArray handles reading/decoding an array of values, starting from the
 688 // current point in the input stream
 689 func (dec *Decoder) readArray() ([]any, error) {
 690     var res []any
 691     // ensure array literal starts correctly
 692     b, err := dec.seekSyntax()
 693     if err != nil {
 694         return res, err
 695     }
 696     if b != '[' {
 697         const fs = `expected a [ to begin an array, but got %s instead`
 698         return res, fmt.Errorf(fs, string(b))
 699     }
 700     dec.skip()
 701 
 702     trailingComma := false
 703 
 704     for {
 705         // handle end of the literal array
 706         b, err := dec.seekSyntax()
 707         if err != nil {
 708             return res, err
 709         }
 710         if b == ']' {
 711             if trailingComma && dec.Strict {
 712                 return res, errTrailingComma
 713             }
 714 
 715             dec.skip()
 716             return res, err
 717         }
 718 
 719         // read current array value
 720         v, err := dec.readValue()
 721         if err != nil {
 722             return res, err
 723         }
 724         res = append(res, v)
 725         trailingComma = false
 726 
 727         // optionally match comma after the current value: unlike strict JSON
 728         // parsers, the way this part works means trailing commas are allowed
 729         b, err = dec.seekSyntax()
 730         if err != nil {
 731             return res, err
 732         }
 733         if b == ',' {
 734             trailingComma = true
 735             dec.skip()
 736         }
 737     }
 738 }
 739 
 740 // readObject handles reading/decoding an key-value-pair object, starting
 741 // from the current point in the input stream
 742 func (dec *Decoder) readObject() (any, error) {
 743     keys, kv, err := dec.buildObject()
 744     if err != nil {
 745         return kv, err
 746     }
 747 
 748     if dec.MakeObject == nil {
 749         return zipObject(keys, kv), nil
 750     }
 751     return dec.MakeObject(keys, kv), nil
 752 }
 753 
 754 func (dec *Decoder) buildObject() (keys []string, kv map[string]any, err error) {
 755     // ensure object literal starts correctly
 756     b, err := dec.seekSyntax()
 757     if err != nil {
 758         return nil, nil, err
 759     }
 760     if b != '{' {
 761         const fs = `expected a { to begin an object, but got %s instead`
 762         return nil, nil, fmt.Errorf(fs, string(b))
 763     }
 764     dec.skip()
 765 
 766     trailingComma := false
 767 
 768     for {
 769         // handle end of the literal object
 770         b, err := dec.seekSyntax()
 771         if err != nil {
 772             return keys, kv, err
 773         }
 774         if b == '}' {
 775             if trailingComma && dec.Strict {
 776                 return keys, kv, errTrailingComma
 777             }
 778 
 779             dec.skip()
 780             return keys, kv, err
 781         }
 782 
 783         // read mandatory string for the next value's key
 784         k, err := dec.readKey(b)
 785         if err != nil {
 786             return keys, kv, err
 787         }
 788         trailingComma = false
 789 
 790         // match mandatory colon after the key
 791         b, err = dec.seekSyntax()
 792         if err != nil {
 793             return keys, kv, err
 794         }
 795         if b != ':' {
 796             const fs = "expected `:` but got %s instead"
 797             err := fmt.Errorf(fs, string(b))
 798             return keys, kv, err
 799         }
 800         dec.skip()
 801 
 802         // read mandatory value after the current key
 803         v, err := dec.readValue()
 804         if err != nil {
 805             return keys, kv, err
 806         }
 807 
 808         _, repeated := kv[k]
 809         if dec.EnforceUniqueKeys && repeated {
 810             const fs = `object-key %q was reused`
 811             return keys, kv, fmt.Errorf(fs, k)
 812         }
 813 
 814         if kv == nil {
 815             kv = make(map[string]any)
 816         }
 817 
 818         if !repeated {
 819             keys = append(keys, k)
 820         }
 821         kv[k] = v
 822 
 823         // optionally match comma after key-value pair: unlike strict
 824         // JSON parsers, this part allows trailing commas
 825         b, err = dec.seekSyntax()
 826         if err != nil {
 827             return keys, kv, err
 828         }
 829         if b == ',' {
 830             trailingComma = true
 831             dec.skip()
 832         }
 833     }
 834 }
 835 
 836 // all the states a Squeezer can be in
 837 const (
 838     // jsonNormal is the state where whitespace and comments are skipped
 839     jsonNormal = 0
 840 
 841     // jsonString is the state inside a string literals's double-quotes
 842     jsonString = 1
 843 
 844     // jsonStringSingle is the state inside a string literals's single-quotes
 845     jsonStringSingle = 2
 846 
 847     // jsonEscape is the brief state to handle backslashes in string literals
 848     jsonEscape = 3
 849 
 850     // jsonComma is the state where a comma was met in the normal state:
 851     // later bytes will determine whether this comma needs to be output
 852     jsonComma = 4
 853 )
 854 
 855 // Squeezer is a JSON-specific io.Reader which ignores unneeded whitespace,
 856 // comments, both single-line and multi-line ones, and even trailing commas in
 857 // arrays and objects; this also makes it a useful data-adapter, beside its
 858 // minimization benefits.
 859 //
 860 // If nothing else, you can just use it as the reader you give to stdlib
 861 // json.NewDecoder, and the stdlib decoder now `supports` common non-standard
 862 // JSON variants, like JSON with Comments (JSONC).
 863 type Squeezer struct {
 864     // r is the buffered source of all bytes
 865     r *bufio.Reader
 866 
 867     // state remembers the current state across calls to method Read
 868     state int
 869 }
 870 
 871 // NewSqueezer is the constructor for type Squeezer.
 872 func NewSqueezer(r io.Reader) Squeezer {
 873     return Squeezer{
 874         state: jsonNormal,
 875         r:     bufio.NewReader(r),
 876     }
 877 }
 878 
 879 // Read implements the io.Reader interface, which is how this type is used
 880 func (js *Squeezer) Read(p []byte) (n int, err error) {
 881     if len(p) < 2 {
 882         const msg = `Read: the buffer given must be at least 2 bytes`
 883         return 0, errors.New(msg)
 884     }
 885 
 886     for len(p) > 0 {
 887         switch js.state {
 888         case jsonNormal:
 889             b, useable, err := js.normal()
 890             if err != nil {
 891                 return n, err
 892             }
 893 
 894             if useable {
 895                 p[0] = b
 896                 p = p[1:]
 897                 n++
 898             }
 899 
 900         case jsonString:
 901             b, err := js.string()
 902             if err != nil {
 903                 return n, err
 904             }
 905             p[0] = b
 906             p = p[1:]
 907             n++
 908 
 909         case jsonStringSingle:
 910             b, err := js.stringSingle()
 911             if err != nil {
 912                 return n, err
 913             }
 914             p[0] = b
 915             p = p[1:]
 916             n++
 917 
 918         case jsonEscape:
 919             b, err := js.escape()
 920             if err != nil {
 921                 return n, err
 922             }
 923             p[0] = b
 924             p = p[1:]
 925             n++
 926 
 927         case jsonComma:
 928             if len(p) < 2 {
 929                 return n, nil
 930             }
 931 
 932             b, commaFirst, useable, err := js.comma()
 933             if err != nil {
 934                 return n, err
 935             }
 936 
 937             if commaFirst {
 938                 p[0] = ','
 939                 p = p[1:]
 940                 n++
 941             }
 942             if useable {
 943                 p[0] = b
 944                 p = p[1:]
 945                 n++
 946             }
 947         }
 948     }
 949 
 950     return n, nil
 951 }
 952 
 953 // skipWhitespace does what it says
 954 func (js *Squeezer) skipWhitespace() (byte, error) {
 955     for {
 956         b, err := js.r.ReadByte()
 957         if err != nil {
 958             return b, err
 959         }
 960 
 961         if !unicode.IsSpace(rune(b)) {
 962             return b, nil
 963         }
 964     }
 965 }
 966 
 967 // normal handles normal/regular bytes for method Read
 968 func (js *Squeezer) normal() (b byte, useable bool, err error) {
 969     b, err = js.skipWhitespace()
 970     if err != nil {
 971         return b, false, err
 972     }
 973 
 974     switch b {
 975     case '"':
 976         js.state = jsonString
 977         return b, true, nil
 978 
 979     case '\'':
 980         js.state = jsonStringSingle
 981         return '"', true, nil
 982 
 983     case ',':
 984         js.state = jsonComma
 985         return b, false, nil
 986 
 987     case '/':
 988         err := js.skipComment()
 989         return 0, false, err
 990 
 991     default:
 992         return b, true, nil
 993     }
 994 }
 995 
 996 // string handles string bytes for method Read
 997 func (js *Squeezer) string() (byte, error) {
 998     b, err := js.r.ReadByte()
 999     if err != nil {
1000         return b, err
1001     }
1002 
1003     switch b {
1004     case '\\':
1005         js.state = jsonEscape
1006         return b, nil
1007 
1008     case '"':
1009         js.state = jsonNormal
1010         return b, nil
1011 
1012     default:
1013         return b, nil
1014     }
1015 }
1016 
1017 // stringSingle handles string bytes delimited by single quotes for method Read
1018 func (js *Squeezer) stringSingle() (byte, error) {
1019     b, err := js.r.ReadByte()
1020     if err != nil {
1021         return b, err
1022     }
1023 
1024     switch b {
1025     case '\\':
1026         js.state = jsonEscape
1027         return b, nil
1028 
1029     case '\'':
1030         js.state = jsonNormal
1031         return '"', nil
1032 
1033     default:
1034         return b, nil
1035     }
1036 }
1037 
1038 // escape handles escaped string bytes for method Read
1039 func (js *Squeezer) escape() (byte, error) {
1040     b, err := js.r.ReadByte()
1041     if err != nil {
1042         return b, err
1043     }
1044 
1045     js.state = jsonString
1046     return b, nil
1047 }
1048 
1049 // comma handles (possibly trailing) commas for method Read; its precondition
1050 // is that the destination buffer in Read still has at least 2 spots to fill
1051 func (js *Squeezer) comma() (b byte, commaFirst, useable bool, err error) {
1052     b, err = js.skipWhitespace()
1053     if err != nil {
1054         return b, false, false, err
1055     }
1056 
1057     switch b {
1058     case ']', '}':
1059         js.state = jsonNormal
1060         return b, false, true, nil
1061 
1062     case '/':
1063         err := js.skipComment()
1064         return b, false, false, err
1065 
1066     case '"':
1067         js.state = jsonString
1068         return b, true, true, nil
1069 
1070     case '\'':
1071         js.state = jsonStringSingle
1072         return '"', true, true, nil
1073 
1074     default:
1075         js.state = jsonNormal
1076         return b, true, true, nil
1077     }
1078 }
1079 
1080 // skipComment does what it says: its precondition is to be called because
1081 // the current byte is `/`  while in the normal state
1082 func (js *Squeezer) skipComment() error {
1083     b, err := js.r.ReadByte()
1084     if err != nil {
1085         return err
1086     }
1087 
1088     switch b {
1089     case '/':
1090         // skip the rest of the line
1091         for {
1092             b, err := js.r.ReadByte()
1093             if err != nil {
1094                 return err
1095             }
1096 
1097             if b == '\n' {
1098                 return nil
1099             }
1100         }
1101 
1102     case '*':
1103         // skip until past `*/`
1104         for ast := false; true; {
1105             b, err := js.r.ReadByte()
1106             if err != nil {
1107                 return err
1108             }
1109 
1110             if ast && b == '/' {
1111                 return nil
1112             }
1113             if b == '*' {
1114                 ast = true
1115             }
1116         }
1117         return nil
1118 
1119     default:
1120         const fs = `expected either // or /*, instead of "/%c"`
1121         return fmt.Errorf(fs, rune(b))
1122     }
1123 }

     File: ./readers_test.go
   1 package json
   2 
   3 import (
   4     "reflect"
   5     "strings"
   6     "testing"
   7 )
   8 
   9 func TestReadJSON(t *testing.T) {
  10     var tests = []struct {
  11         Source   string
  12         Expected any
  13     }{
  14         {`null`, nil},
  15         {`false`, false},
  16         {`true`, true},
  17         {`0`, 0.0},
  18         {`200`, 200.0},
  19         {`-.45`, -0.45},
  20         {`.45`, +0.45},
  21         {`""`, ``},
  22         {`"abc def [\"]"`, `abc def ["]`},
  23         {`"Tom \u0026 Jerry"`, `Tom & Jerry`},
  24         {`"trailing newline\n"`, "trailing newline\n"},
  25         {`"\u001b[48;5;253mabc\u001b[0m"`, "\x1b[48;5;253mabc\x1b[0m"},
  26         {`null // trailing comment`, nil},
  27         {`[]`, []any(nil)},
  28         {`{}`, Object{}},
  29 
  30         {
  31             `[-.34,false,"ehheh",["abc!", 0]]`,
  32             []any{-0.34, false, "ehheh", []any{"abc!", 0.0}},
  33         },
  34         {
  35             `{"abc":123}`,
  36             Object{
  37                 Keys: []string{"abc"},
  38                 Map:  map[string]any{"abc": 123.0},
  39             },
  40         },
  41         {
  42             `{"abc":"def","xyz":123 ,}`,
  43             Object{
  44                 Keys: []string{"abc", "xyz"},
  45                 Map:  map[string]any{"abc": "def", "xyz": 123.0},
  46             },
  47         },
  48     }
  49 
  50     for _, tc := range tests {
  51         t.Run(tc.Source, func(t *testing.T) {
  52             var dec Decoder
  53             got, err := dec.Decode(strings.NewReader(tc.Source))
  54             if err != nil {
  55                 t.Fatal(err)
  56                 return
  57             }
  58 
  59             if !reflect.DeepEqual(got, tc.Expected) {
  60                 const fs = "expected\n%v\nbut got\n%v\ninstead"
  61                 t.Fatalf(fs, tc.Expected, got)
  62                 return
  63             }
  64 
  65             got, err = dec.DecodeBytes([]byte(tc.Source))
  66             if err != nil {
  67                 t.Fatal(err)
  68                 return
  69             }
  70 
  71             if !reflect.DeepEqual(got, tc.Expected) {
  72                 const fs = "expected\n%v\nbut got\n%v\ninstead"
  73                 t.Fatalf(fs, tc.Expected, got)
  74                 return
  75             }
  76         })
  77     }
  78 }

     File: ./strings.go
   1 package json
   2 
   3 import (
   4     "bytes"
   5     "io"
   6     "strings"
   7 )
   8 
   9 var spaces = [256]byte{
  10     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  11     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  12     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  13     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  14     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  15     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  16     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  17     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  18     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  19     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  20     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  21     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  22     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  23     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  24     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  25     32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
  26 }
  27 
  28 var tabs = [256]byte{
  29     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  30     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  31     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  32     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  33     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  34     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  35     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  36     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  37     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  38     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  39     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  40     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  41     9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  42 }
  43 
  44 func ringWrite(w io.Writer, count int, buf []byte) error {
  45     // avoid an infinite loop when counter is 0 and buffer is empty
  46     if count <= 0 || len(buf) == 0 {
  47         return nil
  48     }
  49 
  50     // emit all full-buffer writes
  51     for l := len(buf); count >= l; count -= l {
  52         _, err := w.Write(buf)
  53         if err != nil {
  54             return err
  55         }
  56     }
  57 
  58     // emit any remainder bytes
  59     if count > 0 {
  60         _, err := w.Write(buf[:count])
  61         return err
  62     }
  63     return nil
  64 }
  65 
  66 func needsEscaping(s string) bool {
  67     // return strings.ContainsAny(s, "\"\\/\t\n\r\f\b")
  68     for _, r := range s {
  69         if r < ' ' || r == '\\' || r == '"' {
  70             return true
  71         }
  72     }
  73     return false
  74 }
  75 
  76 func escape(b byte) byte {
  77     switch b {
  78     case 'b':
  79         return '\b'
  80     case 'f':
  81         return '\f'
  82     case 'n':
  83         return '\n'
  84     case 'r':
  85         return '\r'
  86     case 't':
  87         return '\t'
  88     default:
  89         return b
  90     }
  91 }
  92 
  93 func escapeInnerString(sb *strings.Builder, s string) {
  94     for _, r := range s {
  95         switch r {
  96         case '"':
  97             sb.WriteString(`\"`)
  98         case '\\':
  99             sb.WriteString(`\\`)
 100         case '/':
 101             sb.WriteString(`\/`)
 102         case '\b':
 103             sb.WriteString(`\b`)
 104         case '\f':
 105             sb.WriteString(`\f`)
 106         case '\n':
 107             sb.WriteString(`\n`)
 108         case '\r':
 109             sb.WriteString(`\r`)
 110         case '\t':
 111             sb.WriteString(`\t`)
 112 
 113         default:
 114             if r < ' ' {
 115                 const hex = `0123456789abcdef`
 116                 sb.WriteString("\\u00")
 117                 sb.WriteByte(hex[r/16])
 118                 sb.WriteByte(hex[r%16])
 119                 continue
 120             }
 121             sb.WriteRune(r)
 122         }
 123     }
 124 }
 125 
 126 // trimDecimalsByte gets rid of unnecessary trailing decimal 0s, and is only
 127 // meant to be used to trim the results of strconv.AppendFloat
 128 func trimDecimalsByte(s []byte) []byte {
 129     // avoid turning `10000` into `1`, for example
 130     if bytes.IndexByte(s, '.') < 0 {
 131         return s
 132     }
 133 
 134     for bytes.HasSuffix(s, []byte{'0'}) {
 135         s = s[:len(s)-1]
 136     }
 137     return bytes.TrimSuffix(s, []byte{'.'})
 138 }

     File: ./tokenizers.go
   1 package json
   2 
   3 import (
   4     "bytes"
   5     "errors"
   6     "fmt"
   7     "io"
   8     "strconv"
   9     "strings"
  10 )
  11 
  12 // Token is either a syntactically-significant JSON item, or a concrete value
  13 // found in a JSON stream. Among the token types are unquoted identifiers,
  14 // which may or may-not represent strictly-valid JSON.
  15 type Token int
  16 
  17 const (
  18     // NoToken is the uninitialized placeholder-value for the Token type.
  19     NoToken Token = iota
  20 
  21     // IdentifierToken is an unquoted keyword-like strings: JSON only
  22     // allows identifiers/keywords null, false, and true, but you may choose
  23     // to handle this type to allow unquoted object keys and unquoted strings.
  24     IdentifierToken Token = iota
  25 
  26     // Float64Token is a float64 value: you can get the specific value by
  27     // calling Tokenizer.Float64.
  28     Float64Token Token = iota
  29 
  30     // StringToken is a string value: you can get the specific value by
  31     // calling Tokenizer.String.
  32     StringToken Token = iota
  33 
  34     // BeginArrayToken is the `[` which starts an array.
  35     BeginArrayToken Token = iota
  36 
  37     // EndArrayToken is the `]` which ends an array.
  38     EndArrayToken Token = iota
  39 
  40     // BeginArrayToken is the `{` which starts an object.
  41     BeginObjectToken Token = iota
  42 
  43     // EndObjectToken is the `}` which ends an object.
  44     EndObjectToken Token = iota
  45 
  46     // ColonToken is a colon separating keys from values, which is
  47     // found in objects.
  48     ColonToken Token = iota
  49 
  50     // CommaToken is an item-separating comma found in either arrays and/or
  51     // objects.
  52     CommaToken Token = iota
  53 )
  54 
  55 func (t Token) String() string {
  56     switch t {
  57     // case NoToken:
  58     //  return `<none>`
  59     case IdentifierToken:
  60         return `<identifier>`
  61     case Float64Token:
  62         return `<number>`
  63     case StringToken:
  64         return `<string>`
  65     case BeginArrayToken:
  66         return `[`
  67     case EndArrayToken:
  68         return `]`
  69     case BeginObjectToken:
  70         return `{`
  71     case EndObjectToken:
  72         return `}`
  73     case ColonToken:
  74         return `:`
  75     case CommaToken:
  76         return `,`
  77 
  78     default:
  79         return fmt.Sprintf(`undefined token value: %d`, t)
  80     }
  81 }
  82 
  83 // Tokenizer implements a streaming JSON tokenizer which happens to be faster
  84 // than the one in the go stdlib.
  85 //
  86 // Unlike the stdlib, this decoder also supports comments by simply ignoring
  87 // them. Repeating object keys and trailing commas are reported as they're met
  88 // in the stream, so it's up to the user to handle/reject them.
  89 type Tokenizer struct {
  90     r io.Reader
  91 
  92     // buffers
  93     chunk []byte
  94     cur   []byte
  95     n     int64
  96 
  97     // sb is used for string/key values, but also to parse number values
  98     sb strings.Builder
  99 
 100     // current token info
 101     tok Token
 102     err error
 103 
 104     // data for current token, when it's either Float64Token or StringToken
 105     num   float64
 106     str   string
 107     quote rune
 108 }
 109 
 110 // NewTokenizer is the constructor for a Tokenizer.
 111 func NewTokenizer(r io.Reader) Tokenizer {
 112     var t Tokenizer
 113     t.r = r
 114     t.chunk = make([]byte, 128*1024)
 115     t.sb.Grow(64)
 116     return t
 117 }
 118 
 119 // Next advances the input to the next token/part.
 120 func (t *Tokenizer) Next() bool {
 121     if t.r == nil {
 122         return false
 123     }
 124     t.err = t.advance()
 125     return t.err == nil
 126 }
 127 
 128 // Type returns the type of the latest token.
 129 func (t *Tokenizer) Type() Token {
 130     return t.tok
 131 }
 132 
 133 // Err returns the latest error.
 134 func (t *Tokenizer) Err() error {
 135     return t.err
 136 }
 137 
 138 // Float64 returns the latest number value: use it when the latest token type
 139 // is Float64Token.
 140 func (t *Tokenizer) Float64() float64 {
 141     return t.num
 142 }
 143 
 144 // String returns the latest string value: use it when the latest token type
 145 // is StringToken.
 146 func (t *Tokenizer) String() string {
 147     // return t.sb.String()
 148     return t.str
 149 }
 150 
 151 // Quote returns the quoting symbol used in the latest string value read. You
 152 // may want to check this value if you're interested in strict-JSON parsing,
 153 // rejecting strings and/or object-keys which aren't double-quoted.
 154 func (t *Tokenizer) Quote() rune {
 155     return t.quote
 156 }
 157 
 158 // readChunk advances thru the input chunk/block-wise
 159 func (t *Tokenizer) readChunk() error {
 160     n, err := t.r.Read(t.chunk)
 161     if err == io.EOF && n > 0 {
 162         err = nil
 163     }
 164     if err != nil {
 165         return err
 166     }
 167 
 168     t.cur = t.chunk[:n]
 169     if t.n == 0 {
 170         // Give nice errors for all sorts of unsupported Unicode formats,
 171         // whose byte-order-marks are described in the webpage/section below.
 172         //
 173         // Q: Is a BOM used only in 16-bit Unicode text?
 174         // https://www.unicode.org/faq/utf_bom.html
 175 
 176         if bytes.HasPrefix(t.cur, []byte{0x00, 0x00, 0xfe, 0xff}) {
 177             return errors.New(`big-endian UTF-32 not supported, only UTF-8 is`)
 178         }
 179         if bytes.HasPrefix(t.cur, []byte{0xff, 0xfe, 0x00, 0x00}) {
 180             return errors.New(`little-endian UTF-32 not supported, only UTF-8 is`)
 181         }
 182         if bytes.HasPrefix(t.cur, []byte{0xfe, 0xff}) {
 183             return errors.New(`big-endian UTF-16 not supported, only UTF-8 is`)
 184         }
 185         if bytes.HasPrefix(t.cur, []byte{0xff, 0xfe}) {
 186             return errors.New(`little-endian UTF-16 not supported, only UTF-8 is`)
 187         }
 188 
 189         // ignore UTF-8 byte-order-mark, if present
 190         t.cur = bytes.TrimPrefix(t.cur, []byte{0xef, 0xbb, 0xbf})
 191     }
 192 
 193     t.n += int64(n)
 194     return nil
 195 }
 196 
 197 // peek sees what's the next byte, without advancing thru the input
 198 func (t *Tokenizer) peek() (byte, error) {
 199     if len(t.cur) == 0 {
 200         if err := t.readChunk(); err != nil {
 201             return 0, err
 202         }
 203     }
 204 
 205     if len(t.cur) > 0 {
 206         return t.cur[0], nil
 207     }
 208     return 0, io.EOF
 209 }
 210 
 211 // skip advances thru the input 1 byte, unless current buffer is empty: this
 212 // is meant to be used after `peek` succeeds, which ensures buffer isn't empty
 213 func (t *Tokenizer) skip() {
 214     if len(t.cur) > 0 {
 215         t.cur = t.cur[1:]
 216     }
 217 }
 218 
 219 // next peeks at the current byte, and advances after it
 220 func (t *Tokenizer) next() (byte, error) {
 221     b, err := t.peek()
 222     if err != nil {
 223         return b, err
 224     }
 225     t.skip()
 226     return b, nil
 227 }
 228 
 229 // limitbuf returns a length-limited string of the current point in the
 230 // stream: this is useful to give context to some errors
 231 func (t *Tokenizer) limitbuf() string {
 232     const max = 50
 233     if len(t.cur) > max {
 234         return string(t.cur[:max])
 235     }
 236     return string(t.cur)
 237 }
 238 
 239 // seekSyntax skips thru whitespace (syntactic noise) to reach the next
 240 // non-whitespace byte; if already on a non-whitespace byte, it stays there
 241 func (t *Tokenizer) seekSyntax() (byte, error) {
 242     for {
 243         b, err := t.peek()
 244         if err != nil {
 245             return b, err
 246         }
 247 
 248         switch b {
 249         case ' ', '\t', '\n', '\r':
 250             t.skip()
 251 
 252         case '/':
 253             if err := t.skipComment(); err != nil {
 254                 return 0, err
 255             }
 256 
 257         default:
 258             return b, nil
 259         }
 260     }
 261 }
 262 
 263 // skipComment skips either type of comment, single-line or /* ... */
 264 func (t *Tokenizer) skipComment() error {
 265     b, err := t.peek()
 266     if err != nil {
 267         return err
 268     }
 269     if b != '/' {
 270         const fs = `expected start of comment (%s)`
 271         return fmt.Errorf(fs, t.limitbuf())
 272     }
 273     t.skip()
 274 
 275     b, err = t.peek()
 276     if err != nil {
 277         return err
 278     }
 279 
 280     switch b {
 281     case '/':
 282         return t.skipLine()
 283 
 284     case '*':
 285         t.skip()
 286         return t.skipDelimitedComment()
 287 
 288     default:
 289         const fs = `invalid comment syntax after / (%s)`
 290         return fmt.Errorf(fs, t.limitbuf())
 291     }
 292 }
 293 
 294 // skipLine implements skipping/ignoring single-line comments
 295 func (t *Tokenizer) skipLine() error {
 296     for {
 297         b, err := t.peek()
 298         if err != nil {
 299             return err
 300         }
 301         t.skip()
 302 
 303         if b == '\n' {
 304             return nil
 305         }
 306     }
 307 }
 308 
 309 // skipDelimitedComment implements skipping comments which start with /* and
 310 // which end with */
 311 func (t *Tokenizer) skipDelimitedComment() error {
 312     var prev byte
 313     for {
 314         b, err := t.peek()
 315         if err != nil {
 316             return err
 317         }
 318         t.skip()
 319 
 320         if prev == '*' && b == '/' {
 321             return nil
 322         }
 323         prev = b
 324     }
 325 }
 326 
 327 // advance implements method Next
 328 func (t *Tokenizer) advance() error {
 329     b, err := t.seekSyntax()
 330     if b == 0 && err == io.EOF {
 331         t.r = nil
 332         t.err = err
 333         return err
 334     }
 335 
 336     if err != nil {
 337         t.err = err
 338         return err
 339     }
 340 
 341     switch b {
 342     case '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
 343         f, err := t.readFloat64()
 344         t.err = err
 345         if err == nil {
 346             t.tok = Float64Token
 347         }
 348         t.num = f
 349         return err
 350 
 351     case '"', '\'':
 352         s, err := t.readString()
 353         t.err = err
 354         t.tok = StringToken
 355         t.str = s
 356         return err
 357 
 358     case '[':
 359         t.skip()
 360         t.tok = BeginArrayToken
 361         return t.err
 362 
 363     case ']':
 364         t.skip()
 365         t.tok = EndArrayToken
 366         return t.err
 367 
 368     case '{':
 369         t.skip()
 370         t.tok = BeginObjectToken
 371         return t.err
 372 
 373     case '}':
 374         t.skip()
 375         t.tok = EndObjectToken
 376         return t.err
 377 
 378     case ':':
 379         t.skip()
 380         t.tok = ColonToken
 381         return t.err
 382 
 383     case ',':
 384         t.skip()
 385         t.tok = CommaToken
 386         return t.err
 387 
 388     default:
 389         if isIdentSymbol(b) {
 390             t.err = t.readIdent(b)
 391             t.tok = IdentifierToken
 392             t.str = t.sb.String()
 393             return t.err
 394         }
 395         t.err = fmt.Errorf(`invalid rune %c`, b)
 396         return t.err
 397     }
 398 }
 399 
 400 // readFloat64 handles reading/decoding a number, starting from the current
 401 // point in the input stream
 402 func (t *Tokenizer) readFloat64() (float64, error) {
 403     t.sb.Reset()
 404 
 405     for {
 406         b, err := t.peek()
 407         if err == io.EOF {
 408             if b != 0 {
 409                 t.sb.WriteByte(b)
 410             }
 411             return strconv.ParseFloat(t.sb.String(), 64)
 412         }
 413         if err != nil {
 414             return 0, err
 415         }
 416 
 417         digit := '0' <= b && b <= '9'
 418         if digit || b == '+' || b == '-' || b == '.' || b == 'e' || b == 'E' {
 419             t.sb.WriteByte(b)
 420             t.skip()
 421             continue
 422         }
 423 
 424         return strconv.ParseFloat(t.sb.String(), 64)
 425     }
 426 }
 427 
 428 // readString handles reading/decoding a literal string, starting from the
 429 // current point in the input stream
 430 func (t *Tokenizer) readString() (string, error) {
 431     // ensure string literal starts correctly
 432     b, err := t.seekSyntax()
 433     if err != nil {
 434         return ``, err
 435     }
 436 
 437     if b != '"' {
 438         const fs = "expected a \" to begin a string, but got %s instead"
 439         return ``, fmt.Errorf(fs, string(b))
 440     }
 441     if b != '"' && b != '\'' {
 442         const fs = "expected a \" or a ' to begin a string, but got %s instead"
 443         return ``, fmt.Errorf(fs, string(b))
 444     }
 445     t.quote = rune(b)
 446 
 447     quote := b
 448     t.skip()
 449 
 450     num := 0
 451     digits := 0
 452     escaped := false
 453     t.sb.Reset()
 454 
 455     for {
 456         b, err := t.next()
 457         if err == io.EOF {
 458             const fs = `string literal not closed: %q`
 459             err = fmt.Errorf(fs, t.sb.String())
 460         }
 461         if err != nil {
 462             return t.sb.String(), err
 463         }
 464 
 465         if escaped {
 466             escaped = false
 467             if b == 'u' {
 468                 num = 0
 469                 digits = 4
 470                 continue
 471             }
 472             t.sb.WriteByte(escape(b))
 473             continue
 474         }
 475 
 476         if digits > 0 {
 477             d := 0
 478             if '0' <= b && b <= '9' {
 479                 d = int(b - '0')
 480             } else if 'A' <= b && b < 'F' {
 481                 d = int(b-'A') + 10
 482             } else if 'a' <= b && b < 'f' {
 483                 d = int(b-'a') + 10
 484             } else {
 485                 const fs = `invalid string-literal base-16 code: %s`
 486                 return t.sb.String(), fmt.Errorf(fs, string(b))
 487             }
 488 
 489             num *= 16
 490             num += d
 491             digits--
 492 
 493             if digits == 0 {
 494                 t.sb.WriteRune(rune(num))
 495             }
 496             continue
 497         }
 498 
 499         switch b {
 500         case '\\':
 501             escaped = true
 502 
 503         default:
 504             if b == quote {
 505                 if escaped {
 506                     t.sb.WriteByte(quote)
 507                     escaped = false
 508                     continue
 509                 }
 510                 return t.sb.String(), nil
 511             }
 512             t.sb.WriteByte(b)
 513         }
 514     }
 515 }
 516 
 517 // readIdent implements parsing an identifier, or unquoted string-like value:
 518 // this could either be one of the keywords null, false, or true, as well as
 519 // an unquoted object key
 520 func (t *Tokenizer) readIdent(start byte) error {
 521     t.quote = -1
 522     t.sb.Reset()
 523 
 524     for {
 525         b, err := t.peek()
 526         if err == io.EOF {
 527             if b != 0 {
 528                 t.sb.WriteByte(b)
 529             }
 530             return nil
 531         }
 532         if err != nil {
 533             return err
 534         }
 535 
 536         if !isIdentSymbol(b) {
 537             return nil
 538         }
 539 
 540         t.sb.WriteByte(b)
 541         t.skip()
 542     }
 543 }

     File: ./tokenizers_test.go
   1 package json
   2 
   3 import (
   4     "strings"
   5     "testing"
   6 )
   7 
   8 func TestTokenizeJSON(t *testing.T) {
   9     type streamItem struct {
  10         Type  Token
  11         Value any
  12     }
  13 
  14     var tests = []struct {
  15         Source   string
  16         Expected []streamItem
  17     }{
  18         {`null`, []streamItem{{IdentifierToken, `null`}}},
  19         {`false`, []streamItem{{IdentifierToken, `false`}}},
  20         {`true`, []streamItem{{IdentifierToken, `true`}}},
  21         {`0`, []streamItem{{Float64Token, 0.0}}},
  22         {`200`, []streamItem{{Float64Token, 200.0}}},
  23         {`-.45`, []streamItem{{Float64Token, -0.45}}},
  24         {`.45`, []streamItem{{Float64Token, +0.45}}},
  25 
  26         {`null // trailing comment`, []streamItem{{IdentifierToken, `null`}}},
  27 
  28         {`""`, []streamItem{{StringToken, ``}}},
  29         {`"abc def [\"]"`, []streamItem{{StringToken, `abc def ["]`}}},
  30         {`"Tom \u0026 Jerry"`, []streamItem{{StringToken, `Tom & Jerry`}}},
  31         {`"trailing newline\n"`, []streamItem{{StringToken, "trailing newline\n"}}},
  32 
  33         {`[]`, []streamItem{{BeginArrayToken, nil}, {EndArrayToken, nil}}},
  34 
  35         {
  36             `[-.34,false,"ehheh",["abc!", 0]]`,
  37             []streamItem{
  38                 {BeginArrayToken, nil},
  39                 {Float64Token, -0.34},
  40                 {CommaToken, nil},
  41                 {IdentifierToken, `false`},
  42                 {CommaToken, nil},
  43                 {StringToken, `ehheh`},
  44                 {CommaToken, nil},
  45                 {BeginArrayToken, nil},
  46                 {StringToken, `abc!`},
  47                 {CommaToken, nil},
  48                 {Float64Token, 0.0},
  49                 {EndArrayToken, nil},
  50                 {EndArrayToken, nil},
  51             },
  52         },
  53 
  54         {`{}`, []streamItem{{BeginObjectToken, nil}, {EndObjectToken, nil}}},
  55 
  56         {
  57             `{"abc":123}`,
  58             []streamItem{
  59                 {BeginObjectToken, nil},
  60                 {StringToken, `abc`},
  61                 {ColonToken, nil},
  62                 {Float64Token, 123.0},
  63                 {EndObjectToken, nil},
  64             },
  65         },
  66 
  67         {
  68             `{"abc":"def","xyz":123 ,}`,
  69             []streamItem{
  70                 {BeginObjectToken, nil},
  71                 {StringToken, `abc`},
  72                 {ColonToken, nil},
  73                 {StringToken, `def`},
  74                 {CommaToken, nil},
  75                 {StringToken, `xyz`},
  76                 {ColonToken, nil},
  77                 {Float64Token, 123.0},
  78                 {CommaToken, nil},
  79                 {EndObjectToken, nil},
  80             },
  81         },
  82     }
  83 
  84     for _, tc := range tests {
  85         t.Run(tc.Source, func(t *testing.T) {
  86             exp := tc.Expected
  87             jt := NewTokenizer(strings.NewReader(tc.Source))
  88 
  89             for i := 0; jt.Next(); i++ {
  90                 if len(exp) == 0 {
  91                     break
  92                 }
  93 
  94                 if err := jt.Err(); err != nil {
  95                     t.Fatal(err)
  96                     return
  97                 }
  98 
  99                 if exp[0].Type != jt.Type() {
 100                     const fs = `item %d: expected token of type %d, but got %d instead`
 101                     t.Fatalf(fs, i+1, exp[0].Type, jt.Type())
 102                     return
 103                 }
 104 
 105                 switch jt.Type() {
 106                 case IdentifierToken:
 107                     x, ok := exp[0].Value.(string)
 108                     y := jt.String()
 109                     if !ok || x != y {
 110                         const fs = `expected identifier %q, but got %q instead`
 111                         t.Fatalf(fs, x, y)
 112                         return
 113                     }
 114 
 115                 case Float64Token:
 116                     x, ok := exp[0].Value.(float64)
 117                     y := jt.Float64()
 118                     if !ok || x != y {
 119                         const fs = `expected number %f, but got %f instead`
 120                         t.Fatalf(fs, x, y)
 121                         return
 122                     }
 123 
 124                 case StringToken:
 125                     x, ok := exp[0].Value.(string)
 126                     y := jt.String()
 127                     if !ok || x != y {
 128                         const fs = `expected string %q, but got %q instead`
 129                         t.Fatalf(fs, x, y)
 130                         return
 131                     }
 132                 }
 133 
 134                 // advance throught list of expected results
 135                 exp = exp[1:]
 136             }
 137 
 138             if len(exp) > 0 {
 139                 const fs = `still %d items expected: %#v, ...`
 140                 t.Fatalf(fs, len(exp), exp[0])
 141                 return
 142             }
 143         })
 144     }
 145 }

     File: ./values.go
   1 package json
   2 
   3 // Object is an ordered key-value container.
   4 type Object struct {
   5     Keys []string
   6     Map  map[string]any
   7 }
   8 
   9 func zipObject(keys []string, kv map[string]any) any {
  10     return Object{
  11         Keys: keys,
  12         Map:  kv,
  13     }
  14 }

     File: ./writers.go
   1 package json
   2 
   3 import (
   4     "errors"
   5     "fmt"
   6     "io"
   7     "math"
   8     "sort"
   9     "strconv"
  10     "strings"
  11 )
  12 
  13 var (
  14     ErrDoneWriting = errors.New(`can't write any more data`)
  15 
  16     errNaN    = errors.New(`JSON output: NaN not supported`)
  17     errNegInf = errors.New(`JSON output: -Inf not supported`)
  18     errPosInf = errors.New(`JSON output: +Inf not supported`)
  19 )
  20 
  21 // Encoder emits JSON data to a writer.
  22 type Encoder struct {
  23     sb strings.Builder
  24 
  25     // ValueFallback is an optional custom func to try to transform values
  26     // which aren't JSON-compatible into values which are.
  27     //
  28     // Common use-cases are handling float64 NaNs, both float64 infinities,
  29     // and custom ordered-object types. To support the latter, use values of
  30     // type json.Object, which is defined in this package and which encoders
  31     // support directly.
  32     //
  33     // You may also want to handle integers this way, choosing exactly how
  34     // to handle any values out of the exact float64 int-like subset.
  35     ValueFallback func(v any) (any, error)
  36 
  37     // IndentSpaces determines how many spaces to use for each additional
  38     // indentation level. When using this setting, IndentTabs can't be a
  39     // positive value.
  40     //
  41     // When both this setting and IndentTabs are 0, the encoder runs in
  42     // single-line mode, which in turn lets you emit JSON Lines (JSONL /
  43     // NDJSON).
  44     IndentSpaces int
  45 
  46     // IndentTabs determines how many tabs to use for each additional
  47     // indentation level. When using this setting, IndentSpaces can't be
  48     // a positive value.
  49     //
  50     // When both this setting and IndentSpaces are 0, the encoder runs in
  51     // single-line mode, which in turn lets you emit JSON Lines (JSONL /
  52     // NDJSON).
  53     IndentTabs int
  54 
  55     // Compact is to encode JSON without spaces/line-feeds. Single-line mode
  56     // is implied when compact-mode is used, which lets you emit valid JSON
  57     // Lines (JSONL / NDJSON).
  58     Compact bool
  59 }
  60 
  61 // Encode emits the full JSON data representing the value given.
  62 func (enc *Encoder) Encode(w io.Writer, v any) error {
  63     if enc.IndentSpaces > 0 && enc.IndentTabs > 0 {
  64         const msg = `JSON output: both space and tab indentation are enabled`
  65         return errors.New(msg)
  66     }
  67 
  68     buf := spaces[:]
  69     indincr := enc.IndentSpaces
  70     if enc.IndentTabs > 0 {
  71         buf = tabs[:]
  72         indincr = enc.IndentTabs
  73     }
  74     noindents := enc.IndentSpaces < 1 && enc.IndentTabs < 1
  75 
  76     adapt := enc.ValueFallback
  77     if adapt == nil {
  78         adapt = defaultTypeFallback
  79     }
  80 
  81     var comma, colon []byte
  82     if enc.Compact {
  83         comma = []byte{','}
  84         colon = []byte{':'}
  85     } else {
  86         comma = []byte{',', ' '}
  87         colon = []byte{':', ' '}
  88     }
  89 
  90     cfg := encoder{
  91         adaptValue: adapt,
  92 
  93         sb:     &enc.sb,
  94         indbuf: buf,
  95 
  96         comma: comma,
  97         colon: colon,
  98 
  99         indincr: indincr,
 100     }
 101 
 102     singleLine := enc.Compact || noindents
 103     if singleLine {
 104         return cfg.writeValue1Liner(w, v, false)
 105     }
 106     return cfg.writeValue(w, 0, 0, v, false)
 107 }
 108 
 109 // encoder is the actual JSON encoder: its setup is handled by the Encoder
 110 // type, which can expose a nicer public API
 111 type encoder struct {
 112     adaptValue func(any) (any, error)
 113 
 114     sb *strings.Builder
 115 
 116     // ring-buffer to use for indentation, while minimizing calls to Write
 117     indbuf []byte
 118 
 119     comma []byte
 120     colon []byte
 121 
 122     // how much to add to the current indentation when entering a new level
 123     indincr int
 124 }
 125 
 126 // writeValue1Liner is similar to func writeValue, except it constrains
 127 // output to a single line
 128 func (enc *encoder) writeValue1Liner(w io.Writer, v any, retrying bool) error {
 129     switch x := v.(type) {
 130     case nil:
 131         return enc.writeNull(w, 0)
 132     case bool:
 133         return enc.writeBoolean(w, 0, x)
 134     case float64:
 135         return enc.writeNumber1Liner(w, x, retrying)
 136     case string:
 137         return enc.writeString(w, 0, x)
 138     case []any:
 139         return enc.writeArray1Liner(w, x)
 140     case Object:
 141         return enc.writeObject1Liner(w, x)
 142     default:
 143         return enc.writeOther1Liner(w, x, retrying)
 144     }
 145 }
 146 
 147 // writeNumber1Liner handles numbers for func writeValue1Liner
 148 func (enc *encoder) writeNumber1Liner(w io.Writer, x float64, retrying bool) error {
 149     if retrying {
 150         // avoid infinite loops
 151         if math.IsNaN(x) {
 152             return errNaN
 153         }
 154         if math.IsInf(x, -1) {
 155             return errNegInf
 156         }
 157         if math.IsInf(x, +1) {
 158             return errPosInf
 159         }
 160     }
 161 
 162     if math.IsNaN(x) {
 163         v, err := enc.adaptValue(x)
 164         if err != nil {
 165             return err
 166         }
 167         return enc.writeValue1Liner(w, v, true)
 168     }
 169 
 170     if math.IsInf(x, 0) {
 171         v, err := enc.adaptValue(x)
 172         if err != nil {
 173             return err
 174         }
 175         return enc.writeValue1Liner(w, v, true)
 176     }
 177 
 178     return writeValidNumber(w, x)
 179 }
 180 
 181 // writeArray1Liner handles arrays for func writeValue1Liner
 182 func (enc *encoder) writeArray1Liner(w io.Writer, x []any) error {
 183     w.Write([]byte{'['})
 184     for i, v := range x {
 185         if i > 0 {
 186             w.Write(enc.comma)
 187         }
 188 
 189         err := enc.writeValue1Liner(w, v, false)
 190         if err != nil {
 191             return err
 192         }
 193     }
 194     return writeByte(w, ']')
 195 }
 196 
 197 // writeObject1Liner handles objects for func writeValue1Liner
 198 func (enc *encoder) writeObject1Liner(w io.Writer, x Object) error {
 199     w.Write([]byte{'{'})
 200     for i, k := range x.Keys {
 201         if i > 0 {
 202             w.Write(enc.comma)
 203         }
 204 
 205         err := enc.writeString(w, 0, k)
 206         if err != nil {
 207             return err
 208         }
 209 
 210         w.Write(enc.colon)
 211 
 212         err = enc.writeValue1Liner(w, x.Map[k], false)
 213         if err != nil {
 214             return err
 215         }
 216     }
 217     return writeByte(w, '}')
 218 }
 219 
 220 // writeOther1Liner handles other/unsupported values for func writeValue1Liner
 221 func (enc *encoder) writeOther1Liner(w io.Writer, v any, retrying bool) error {
 222     if retrying {
 223         // avoid infinite loops
 224         return unsupportedType(v)
 225     }
 226 
 227     v, err := enc.adaptValue(v)
 228     if err != nil {
 229         return err
 230     }
 231     return enc.writeValue1Liner(w, v, true)
 232 }
 233 
 234 // writeValue tries to emit any supported JSON type, using the fallback value
 235 // adapter given
 236 func (enc *encoder) writeValue(w io.Writer, lead, indent int, v any, retrying bool) error {
 237     switch x := v.(type) {
 238     case nil:
 239         return enc.writeNull(w, lead)
 240     case bool:
 241         return enc.writeBoolean(w, lead, x)
 242     case float64:
 243         return enc.writeNumber(w, lead, x, retrying)
 244     case string:
 245         return enc.writeString(w, lead, x)
 246     case []any:
 247         return enc.writeArray(w, lead, indent, x)
 248     case Object:
 249         return enc.writeObject(w, lead, indent, x)
 250     default:
 251         return enc.writeOther(w, lead, indent, x, retrying)
 252     }
 253 }
 254 
 255 // writeNull handles null values for funcs writeValue and writeValue1Liner
 256 func (enc *encoder) writeNull(w io.Writer, indent int) error {
 257     ringWrite(w, indent, enc.indbuf)
 258     return write(w, []byte{'n', 'u', 'l', 'l'})
 259 }
 260 
 261 // writeBoolean handles boolean values for funcs writeValue and writeValue1Liner
 262 func (enc *encoder) writeBoolean(w io.Writer, indent int, v bool) error {
 263     ringWrite(w, indent, enc.indbuf)
 264     if v {
 265         return write(w, []byte{'t', 'r', 'u', 'e'})
 266     }
 267     return write(w, []byte{'f', 'a', 'l', 's', 'e'})
 268 }
 269 
 270 // writeNumber handles numbers for func writeValue
 271 func (enc *encoder) writeNumber(w io.Writer, indent int, x float64, retrying bool) error {
 272     if retrying {
 273         // avoid infinite loops
 274         if math.IsNaN(x) {
 275             return errNaN
 276         }
 277         if math.IsInf(x, -1) {
 278             return errNegInf
 279         }
 280         if math.IsInf(x, +1) {
 281             return errPosInf
 282         }
 283     }
 284 
 285     if math.IsNaN(x) {
 286         v, err := enc.adaptValue(x)
 287         if err != nil {
 288             return err
 289         }
 290         return enc.writeValue(w, indent, indent, v, true)
 291     }
 292 
 293     if math.IsInf(x, 0) {
 294         v, err := enc.adaptValue(x)
 295         if err != nil {
 296             return err
 297         }
 298         return enc.writeValue(w, indent, indent, v, true)
 299     }
 300 
 301     ringWrite(w, indent, enc.indbuf)
 302     return writeValidNumber(w, x)
 303 }
 304 
 305 // writeValidNumber handles JSON-compatible float64 values for func writeNumber
 306 // and func writeNumber1Liner
 307 func writeValidNumber(w io.Writer, x float64) error {
 308     var b [32]byte
 309     s := strconv.AppendFloat(b[:0], x, 'f', 16, 64)
 310     return write(w, trimDecimalsByte(s))
 311 }
 312 
 313 // writeString handles strings for funcs writeValue and writeValue1Liner
 314 func (enc *encoder) writeString(w io.Writer, indent int, s string) error {
 315     ringWrite(w, indent, enc.indbuf)
 316     if !needsEscaping(s) {
 317         w.Write([]byte{'"'})
 318         fmt.Fprint(w, s)
 319         return writeByte(w, '"')
 320     }
 321 
 322     enc.sb.Reset()
 323     escapeInnerString(enc.sb, s)
 324 
 325     w.Write([]byte{'"'})
 326     fmt.Fprint(w, enc.sb.String())
 327     return writeByte(w, '"')
 328 }
 329 
 330 // writeArray handles arrays for func writeValue
 331 func (enc *encoder) writeArray(w io.Writer, lead, indent int, x []any) error {
 332     if len(x) == 0 {
 333         // because otherwise "[\n\n]" is emitted
 334         ringWrite(w, lead, enc.indbuf)
 335         return write(w, []byte{'[', ']'})
 336     }
 337 
 338     ringWrite(w, lead, enc.indbuf)
 339     w.Write([]byte{'[', '\n'})
 340     next := indent + enc.indincr
 341 
 342     for i, v := range x {
 343         if i > 0 {
 344             w.Write([]byte{',', '\n'})
 345         }
 346 
 347         err := enc.writeValue(w, next, next, v, false)
 348         if err != nil {
 349             return err
 350         }
 351     }
 352 
 353     w.Write([]byte{'\n'})
 354     ringWrite(w, indent, enc.indbuf)
 355     return writeByte(w, ']')
 356 }
 357 
 358 // writeObject handles objects for func writeValue
 359 func (enc *encoder) writeObject(w io.Writer, lead, indent int, x Object) error {
 360     if len(x.Keys) == 0 {
 361         // because otherwise "{\n\n}" is emitted
 362         ringWrite(w, lead, enc.indbuf)
 363         return write(w, []byte{'{', '}'})
 364     }
 365 
 366     ringWrite(w, lead, enc.indbuf)
 367     w.Write([]byte{'{', '\n'})
 368     next := indent + enc.indincr
 369 
 370     for i, k := range x.Keys {
 371         if i > 0 {
 372             w.Write([]byte{',', '\n'})
 373         }
 374 
 375         ringWrite(w, next, enc.indbuf)
 376 
 377         err := enc.writeString(w, 0, k)
 378         if err != nil {
 379             return err
 380         }
 381 
 382         w.Write(enc.colon)
 383 
 384         err = enc.writeValue(w, 0, next, x.Map[k], false)
 385         if err != nil {
 386             return err
 387         }
 388     }
 389 
 390     w.Write([]byte{'\n'})
 391     ringWrite(w, indent, enc.indbuf)
 392     return writeByte(w, '}')
 393 }
 394 
 395 // writeOther handles other/unsupported values for func writeValue
 396 func (enc *encoder) writeOther(w io.Writer, lead, indent int, v any, retrying bool) error {
 397     if retrying {
 398         // avoid infinite loops
 399         return unsupportedType(v)
 400     }
 401 
 402     v, err := enc.adaptValue(v)
 403     if err != nil {
 404         return err
 405     }
 406     return enc.writeValue(w, lead, indent, v, true)
 407 }
 408 
 409 // defaultTypeFallback is used as an encoder's TypeFallback func when none
 410 // is given/setup before calling Encode
 411 func defaultTypeFallback(v any) (any, error) {
 412     switch v := v.(type) {
 413     case float64:
 414         if math.IsNaN(v) {
 415             return nil, errNaN
 416         }
 417         if math.IsInf(v, -1) {
 418             return nil, errNegInf
 419         }
 420         if math.IsInf(v, +1) {
 421             return nil, errPosInf
 422         }
 423         return v, nil
 424 
 425     case map[string]any:
 426         return MapFallback(v), nil
 427 
 428     default:
 429         return nil, unsupportedType(v)
 430     }
 431 }
 432 
 433 // Float64Fallback helps you handle the few invalid-JSON float64 values: use
 434 // it in your TypeFallback funcs, in the appropriate type switch-case.
 435 //
 436 // The extra arguments given are substitutes for these invalid float64 values,
 437 // namely the NaNs, negative infinity, and positive infinity.
 438 func Float64Fallback(f float64, nan, neginf, posinf any) any {
 439     if math.IsNaN(f) {
 440         return nan
 441     }
 442     if math.IsInf(f, -1) {
 443         return neginf
 444     }
 445     if math.IsInf(f, +1) {
 446         return posinf
 447     }
 448     return f
 449 }
 450 
 451 // MapFallback helps you correctly handle map[string]any types in your custom
 452 // TypeFallback funcs: just use it in the appropriate type switch-case. This
 453 // func is just one way to do it, which happens to work in all situations.
 454 //
 455 // If a specific non-sorted key-order is needed, you'll have to make your own
 456 // value of type json.Object, also from this package.
 457 //
 458 // This func allocates slices: while that may seem wasteful on a hasty look,
 459 // remember that JSON objects can contain other objects, whether directly or
 460 // not; when that happens, reusing a string-slice for the sorted keys won't
 461 // work correctly.
 462 func MapFallback(kv map[string]any) Object {
 463     keys := make([]string, 0, len(kv))
 464     for k := range kv {
 465         keys = append(keys, k)
 466     }
 467     sort.Strings(keys)
 468     return Object{Keys: keys, Map: kv}
 469 }
 470 
 471 // unsupportedType standardizes an error message used in multiple places
 472 func unsupportedType(v any) error {
 473     const fs = `JSON output: unsupported type %T`
 474     return fmt.Errorf(fs, v)
 475 }
 476 
 477 // write emit a single byte, emitting a custom ignore-me-type error, to stop
 478 // things early if the writer can't receive any more data
 479 func writeByte(w io.Writer, b byte) error {
 480     _, err := w.Write([]byte{b})
 481     if err != nil {
 482         return ErrDoneWriting
 483     }
 484     return nil
 485 }
 486 
 487 // write emits bytes, emitting a custom ignore-me-type error, to stop things
 488 // early if the writer can't receive any more data
 489 func write(w io.Writer, p []byte) error {
 490     n, err := w.Write(p)
 491     if err != nil && n < 1 {
 492         return ErrDoneWriting
 493     }
 494     return nil
 495 }

     File: ./writers_test.go
   1 package json
   2 
   3 import (
   4     "fmt"
   5     "strings"
   6     "testing"
   7 )
   8 
   9 func TestWriteJSON(t *testing.T) {
  10     var tests = []struct {
  11         Value   any
  12         Normal  string
  13         Compact string
  14     }{
  15         {nil, `null`, `null`},
  16         {false, `false`, `false`},
  17         {true, `true`, `true`},
  18         {0.0, `0`, `0`},
  19         {200.0, `200`, `200`},
  20         {-0.45, `-0.45`, `-0.45`},
  21         {+0.45, `0.45`, `0.45`},
  22         {-45.0, `-45`, `-45`},
  23         {+45.0, `45`, `45`},
  24         {``, `""`, `""`},
  25         {`abc def ["]`, `"abc def [\"]"`, `"abc def [\"]"`},
  26         {`Tom & Jerry`, `"Tom & Jerry"`, `"Tom & Jerry"`},
  27         {"trailing newline\n", `"trailing newline\n"`, `"trailing newline\n"`},
  28 
  29         {[]any{}, `[]`, `[]`},
  30         {
  31             []any{-0.34, false, "ehheh", []any{"abc!", 0.0}},
  32             `[-0.34, false, "ehheh", ["abc!", 0]]`,
  33             `[-0.34,false,"ehheh",["abc!",0]]`,
  34         },
  35 
  36         {Object{Map: make(map[string]any)}, `{}`, `{}`},
  37         {
  38             Object{
  39                 Keys: []string{`abc`},
  40                 Map:  map[string]any{`abc`: 123.0},
  41             },
  42             `{"abc": 123}`,
  43             `{"abc":123}`,
  44         },
  45         {
  46             Object{
  47                 Keys: []string{`abc`, `xyz`},
  48                 Map:  map[string]any{`abc`: `def`, `xyz`: 123.0},
  49             },
  50             `{"abc": "def", "xyz": 123}`,
  51             `{"abc":"def","xyz":123}`,
  52         },
  53     }
  54 
  55     for _, tc := range tests {
  56         t.Run(tc.Normal, func(t *testing.T) {
  57             var enc Encoder
  58             var out strings.