| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build goexperiment.jsonv2 |
| |
| package jsontext |
| |
| import ( |
| "bytes" |
| "errors" |
| "math" |
| "strconv" |
| |
| "encoding/json/internal/jsonflags" |
| "encoding/json/internal/jsonwire" |
| ) |
| |
| // NOTE: Token is analogous to v1 json.Token. |
| |
| const ( |
| maxInt64 = math.MaxInt64 |
| minInt64 = math.MinInt64 |
| maxUint64 = math.MaxUint64 |
| minUint64 = 0 // for consistency and readability purposes |
| |
| invalidTokenPanic = "invalid jsontext.Token; it has been voided by a subsequent json.Decoder call" |
| ) |
| |
| var errInvalidToken = errors.New("invalid jsontext.Token") |
| |
| // Token represents a lexical JSON token, which may be one of the following: |
| // - a JSON literal (i.e., null, true, or false) |
| // - a JSON string (e.g., "hello, world!") |
| // - a JSON number (e.g., 123.456) |
| // - a begin or end delimiter for a JSON object (i.e., { or } ) |
| // - a begin or end delimiter for a JSON array (i.e., [ or ] ) |
| // |
| // A Token cannot represent entire array or object values, while a [Value] can. |
| // There is no Token to represent commas and colons since |
| // these structural tokens can be inferred from the surrounding context. |
| // |
| // A Token stores data in one of two forms: |
| // |
| // - As raw JSON text: backed by the internal buffer of the [Decoder] |
| // and only ever produced by [Decoder.ReadToken]. |
| // Such a token is only valid until the next call to any method on that |
| // [Decoder] (e.g., [Decoder.PeekKind], [Decoder.ReadToken], |
| // [Decoder.ReadValue], or [Decoder.SkipValue]). |
| // Call [Token.Clone] to copy the raw text into an independent allocation |
| // that persists beyond subsequent [Decoder] calls. |
| // |
| // - As a typed Go value: a self-contained representation produced by |
| // the constructor functions (e.g., [String], [Int], [Uint], [Float]). |
| // Such tokens are valid indefinitely and do not need to be cloned. |
| type Token struct { |
| nonComparable |
| |
| // Tokens can exist in either a "raw" or an "exact" form. |
| // Tokens produced by the Decoder are in the "raw" form. |
| // Tokens returned by constructors are usually in the "exact" form. |
| // The Encoder accepts Tokens in either the "raw" or "exact" form. |
| // |
| // The following chart shows the possible values for each Token type: |
| // ╔══════════════════╦════════════╤════════════╤════════════╗ |
| // ║ Token type ║ raw field │ str field │ num field ║ |
| // ╠══════════════════╬════════════╪════════════╪════════════╣ |
| // ║ null (raw) ║ "null" │ "" │ 0 ║ |
| // ║ false (raw) ║ "false" │ "" │ 0 ║ |
| // ║ true (raw) ║ "true" │ "" │ 0 ║ |
| // ║ string (raw) ║ non-empty │ "" │ offset ║ |
| // ║ string (string) ║ nil │ non-empty │ 0 ║ |
| // ║ number (raw) ║ non-empty │ "" │ offset ║ |
| // ║ number (float32) ║ nil │ "F" │ non-zero ║ |
| // ║ number (float64) ║ nil │ "f" │ non-zero ║ |
| // ║ number (int64) ║ nil │ "i" │ non-zero ║ |
| // ║ number (uint64) ║ nil │ "u" │ non-zero ║ |
| // ║ object (delim) ║ "{" or "}" │ "" │ 0 ║ |
| // ║ array (delim) ║ "[" or "]" │ "" │ 0 ║ |
| // ╚══════════════════╩════════════╧════════════╧════════════╝ |
| // |
| // Notes: |
| // - For tokens stored in "raw" form, the num field contains the |
| // absolute offset determined by raw.previousOffsetStart(). |
| // The buffer itself is stored in raw.previousBuffer(). |
| // - JSON literals and structural characters are always in the "raw" form. |
| // - JSON strings and numbers can be in either "raw" or "exact" forms. |
| // - The exact zero value of JSON strings and numbers in the "exact" forms |
| // have ambiguous representation. Thus, they are always represented |
| // in the "raw" form. |
| |
| // raw contains a reference to the raw decode buffer. |
| // If non-nil, then its value takes precedence over str and num. |
| // It is only valid if num == raw.previousOffsetStart(). |
| raw *decodeBuffer |
| |
| // str is the unescaped JSON string if num is zero. |
| // Otherwise, it is "F", "f", "i", or "u" if num should be interpreted |
| // as a float32, float64, int64, or uint64, respectively. |
| str string |
| |
| // num is a float32, float64, int64, or uint64 stored as a uint64 value. |
| // For floating-point values, it stores the raw IEEE-754 bit-pattern. |
| // It is non-zero for any JSON number in the "exact" form. |
| num uint64 |
| } |
| |
| // TODO: Does representing 1-byte delimiters as *decodeBuffer cause performance issues? |
| |
| var ( |
| Null Token = rawToken("null") |
| False Token = rawToken("false") |
| True Token = rawToken("true") |
| |
| BeginObject Token = rawToken("{") |
| EndObject Token = rawToken("}") |
| BeginArray Token = rawToken("[") |
| EndArray Token = rawToken("]") |
| |
| zeroString Token = rawToken(`""`) |
| zeroNumber Token = rawToken(`0`) |
| |
| nanString Token = String("NaN") |
| pinfString Token = String("Infinity") |
| ninfString Token = String("-Infinity") |
| ) |
| |
| func rawToken(s string) Token { |
| return Token{raw: &decodeBuffer{buf: []byte(s), prevStart: 0, prevEnd: len(s)}} |
| } |
| |
| // Bool constructs a Token representing a JSON boolean. |
| func Bool(b bool) Token { |
| if b { |
| return True |
| } |
| return False |
| } |
| |
| // String constructs a Token representing a JSON string. |
| // The provided string should contain valid UTF-8, otherwise invalid characters |
| // may be mangled as the Unicode replacement character. |
| func String(s string) Token { |
| if len(s) == 0 { |
| return zeroString |
| } |
| return Token{str: s} |
| } |
| |
| // Float32 constructs a Token representing a JSON number as |
| // a 32-bit floating-point number formatted according to |
| // ECMA-262, 10th edition, section 7.1.12.1, |
| // with the exception that -0 is still formatted as -0. |
| // The values NaN, +Inf, and -Inf will be represented |
| // as a JSON string with the values "NaN", "Infinity", and "-Infinity". |
| // |
| // Note that most JSON libraries and standards assume that JSON numbers |
| // are 64-bit floating-point numbers. Use of 32-bit precision should |
| // only be used if the corresponding decoder knows that |
| // this JSON number token is expected to only have 32-bit precision. |
| // For all other situations, prefer using the [Float] constructor instead. |
| func Float32(n float32) Token { |
| if n != 0 && !math.IsNaN(float64(n)) && !math.IsInf(float64(n), 0) { |
| return Token{str: "F", num: uint64(math.Float32bits(n))} |
| } |
| return Float(float64(n)) // handles ±0, NaN, and ±Inf |
| } |
| |
| // Float constructs a Token representing a JSON number as |
| // a 64-bit floating-point number formatted according to |
| // ECMA-262, 10th edition, section 7.1.12.1 and RFC 8785, section 3.2.2.3. |
| // with the exception that -0 is still formatted as -0. |
| // The values NaN, +Inf, and -Inf will be represented |
| // as a JSON string with the values "NaN", "Infinity", and "-Infinity". |
| func Float(n float64) Token { |
| switch { |
| case math.Float64bits(n) == 0: |
| return zeroNumber |
| case math.IsNaN(n): |
| return nanString |
| case math.IsInf(n, +1): |
| return pinfString |
| case math.IsInf(n, -1): |
| return ninfString |
| } |
| return Token{str: "f", num: math.Float64bits(n)} |
| } |
| |
| // Int constructs a Token representing a JSON number from an int64. |
| func Int(n int64) Token { |
| if n == 0 { |
| return zeroNumber |
| } |
| return Token{str: "i", num: uint64(n)} |
| } |
| |
| // Uint constructs a Token representing a JSON number from a uint64. |
| func Uint(n uint64) Token { |
| if n == 0 { |
| return zeroNumber |
| } |
| return Token{str: "u", num: uint64(n)} |
| } |
| |
| // Clone returns a copy of the token with a value that is not backed by the |
| // [Decoder] buffer and therefore remains valid past subsequent [Decoder] calls. |
| // It has no effect on tokens produced by constructor functions, |
| // since those are already self-contained. |
| func (t Token) Clone() Token { |
| // TODO: Allow caller to avoid any allocations? |
| if raw := t.raw; raw != nil { |
| // Avoid copying globals. |
| if t.raw.prevStart == 0 { |
| switch t.raw { |
| case Null.raw: |
| return Null |
| case False.raw: |
| return False |
| case True.raw: |
| return True |
| case BeginObject.raw: |
| return BeginObject |
| case EndObject.raw: |
| return EndObject |
| case BeginArray.raw: |
| return BeginArray |
| case EndArray.raw: |
| return EndArray |
| } |
| } |
| |
| // This only ever needs to clone strings and numbers. |
| if uint64(raw.previousOffsetStart()) != t.num { |
| panic(invalidTokenPanic) |
| } |
| buf := bytes.Clone(raw.previousBuffer()) |
| return Token{raw: &decodeBuffer{buf: buf, prevStart: 0, prevEnd: len(buf)}} |
| } |
| return t |
| } |
| |
| // Bool returns the value for a JSON boolean. |
| // It panics if the token kind is not a JSON boolean. |
| func (t Token) Bool() bool { |
| switch t.raw { |
| case True.raw: |
| return true |
| case False.raw: |
| return false |
| default: |
| panic("invalid JSON token kind: " + t.Kind().String()) |
| } |
| } |
| |
| // appendString appends a JSON string to dst and returns it. |
| // It panics if t is not a JSON string. |
| func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error) { |
| if raw := t.raw; raw != nil { |
| // Handle raw string value. |
| buf := raw.previousBuffer() |
| if Kind(buf[0]) == '"' { |
| if jsonwire.ConsumeSimpleString(buf) == len(buf) { |
| return append(dst, buf...), nil |
| } |
| dst, _, err := jsonwire.ReformatString(dst, buf, flags) |
| return dst, err |
| } |
| } else if len(t.str) != 0 && t.num == 0 { |
| // Handle exact string value. |
| return jsonwire.AppendQuote(dst, []byte(t.str), flags) |
| } |
| |
| panic("invalid JSON token kind: " + t.Kind().String()) |
| } |
| |
| // String returns the unescaped string value for a JSON string. |
| // For other JSON kinds, this returns the raw JSON representation. |
| func (t Token) String() string { |
| // This is inlinable to take advantage of "function outlining". |
| // This avoids an allocation for the string(b) conversion |
| // if the caller does not use the string in an escaping manner. |
| // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/ |
| s, b := t.string() |
| if len(b) > 0 { |
| return string(b) |
| } |
| return s |
| } |
| func (t Token) string() (string, []byte) { |
| if raw := t.raw; raw != nil { |
| if uint64(raw.previousOffsetStart()) != t.num { |
| panic(invalidTokenPanic) |
| } |
| buf := raw.previousBuffer() |
| if buf[0] == '"' { |
| // TODO: Preserve ValueFlags in Token? |
| isVerbatim := jsonwire.ConsumeSimpleString(buf) == len(buf) |
| return "", jsonwire.UnquoteMayCopy(buf, isVerbatim) |
| } |
| // Handle tokens that are not JSON strings for fmt.Stringer. |
| return "", buf |
| } |
| if len(t.str) != 0 && t.num == 0 { |
| return t.str, nil |
| } |
| // Handle tokens that are not JSON strings for fmt.Stringer. |
| if t.num > 0 { |
| switch t.str[0] { |
| case 'F': |
| return string(jsonwire.AppendFloat(nil, float64(math.Float32frombits(uint32(t.num))), 32)), nil |
| case 'f': |
| return string(jsonwire.AppendFloat(nil, float64(math.Float64frombits(uint64(t.num))), 64)), nil |
| case 'i': |
| return strconv.FormatInt(int64(t.num), 10), nil |
| case 'u': |
| return strconv.FormatUint(uint64(t.num), 10), nil |
| } |
| } |
| return "<invalid jsontext.Token>", nil |
| } |
| |
| // appendNumber appends a JSON number to dst and returns it. |
| // It panics if t is not a JSON number. |
| func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error) { |
| if raw := t.raw; raw != nil { |
| // Handle raw number value. |
| buf := raw.previousBuffer() |
| if Kind(buf[0]).normalize() == '0' { |
| dst, _, err := jsonwire.ReformatNumber(dst, buf, flags) |
| return dst, err |
| } |
| } else if t.num != 0 { |
| // Handle exact number value. |
| switch t.str[0] { |
| case 'F': |
| return jsonwire.AppendFloat(dst, float64(math.Float32frombits(uint32(t.num))), 32), nil |
| case 'f': |
| return jsonwire.AppendFloat(dst, float64(math.Float64frombits(uint64(t.num))), 64), nil |
| case 'i': |
| return strconv.AppendInt(dst, int64(t.num), 10), nil |
| case 'u': |
| return strconv.AppendUint(dst, uint64(t.num), 10), nil |
| } |
| } |
| |
| panic("invalid JSON token kind: " + t.Kind().String()) |
| } |
| |
| // Float32 returns the floating-point value for a JSON number |
| // parsed according to 32 bits of precision. |
| // |
| // If the JSON number is outside the representable range of a float32, |
| // it returns +Inf or -Inf along with an error |
| // that matches [strconv.ErrRange] according to [errors.Is]. |
| // |
| // It returns a NaN, +Inf, or -Inf value for any JSON string |
| // with the values "NaN", "Infinity", or "-Infinity". |
| // |
| // It panics if the token kind is not a JSON number |
| // or a JSON string with the aforementioned values. |
| // |
| // Note that most JSON libraries and standards assume that JSON numbers |
| // are 64-bit floating-point numbers. |
| // This method should only be used if the caller knows |
| // from other context that this token is a JSON number |
| // formatted only to 32 bits of precision (such as being encoded |
| // using the [Float32] constructor). For all other situations, |
| // prefer using the [Token.Float] accessor instead. |
| func (t Token) Float32() (float32, error) { |
| f, err := t.float(32) |
| return float32(f), err |
| } |
| |
| // Float returns the floating-point value for a JSON number |
| // parsed according to 64 bits of precision. |
| // |
| // If the JSON number is outside the representable range of a float64, |
| // it returns +Inf or -Inf along with an error |
| // that matches [strconv.ErrRange] according to [errors.Is]. |
| // |
| // It returns a NaN, +Inf, or -Inf value for any JSON string |
| // with the values "NaN", "Infinity", or "-Infinity". |
| // |
| // It panics if the token kind is not a JSON number |
| // or a JSON string with the aforementioned values. |
| func (t Token) Float() (float64, error) { |
| f, err := t.float(64) |
| return float64(f), err |
| } |
| |
| func (t Token) float(bits int) (float64, error) { |
| if raw := t.raw; raw != nil { |
| // Handle raw JSON number value. |
| if uint64(raw.previousOffsetStart()) != t.num { |
| panic(invalidTokenPanic) |
| } |
| buf := raw.previousBuffer() |
| if Kind(buf[0]).normalize() == '0' { |
| fv, err := strconv.ParseFloat(string(buf), bits) |
| if err != nil { |
| err = &numError{accessor: "Float", value: t.String(), err: errors.Unwrap(err)} // only ever ErrRange |
| } |
| return fv, err |
| } |
| } else if t.num != 0 { |
| // Handle typed Go number value. |
| switch t.str[0] { |
| case 'F': |
| return float64(math.Float32frombits(uint32(t.num))), nil |
| case 'f': |
| f64 := float64(math.Float64frombits(uint64(t.num))) |
| if bits == 32 && !math.IsInf(f64, 0) && math.IsInf(float64(float32(f64)), 0) { |
| return f64, &numError{accessor: "Float", value: t.String(), err: strconv.ErrRange} |
| } |
| return f64, nil |
| case 'i': |
| return float64(int64(t.num)), nil // NOTE: This may lead to loss of precision. |
| case 'u': |
| return float64(uint64(t.num)), nil // NOTE: This may lead to loss of precision. |
| } |
| } |
| |
| // Handle string values with "NaN", "Infinity", or "-Infinity". |
| if t.Kind() == '"' { |
| switch t.String() { |
| case "NaN": |
| return math.NaN(), nil |
| case "Infinity": |
| return math.Inf(+1), nil |
| case "-Infinity": |
| return math.Inf(-1), nil |
| } |
| // TODO: Should this be a error instead of a panic? |
| // We can safely switch from a panic to an error in the future. |
| } |
| |
| panic("invalid JSON token kind: " + t.Kind().String()) |
| } |
| |
| // Int returns the signed integer value for a JSON number. |
| // |
| // It reports an error that matches [strconv.ErrSyntax] according to [errors.Is] |
| // if the JSON number does not match the restricted grammar of just a signed integer. |
| // It reports an error that matches [strconv.ErrRange] according to [errors.Is] |
| // if the JSON number is a signed integer, but outside the range of an int64. |
| // Even if an error is reported, a reasonable value is still returned. |
| // The fractional component of any number is ignored (truncation toward zero). |
| // Any number beyond the representation of an int64 will be saturated |
| // to the closest representable value. |
| // |
| // It panics if the token kind is not a JSON number. |
| func (t Token) Int() (int64, error) { |
| if raw := t.raw; raw != nil { |
| // Handle raw JSON number value. |
| if uint64(raw.previousOffsetStart()) != t.num { |
| panic(invalidTokenPanic) |
| } |
| buf := raw.previousBuffer() |
| if len(buf) > 0 && buf[0] == '-' { |
| // Prospectively parse a negative integer. |
| switch abs, ok := jsonwire.ParseUint(buf[len("-"):]); { |
| case abs > -minInt64: |
| return minInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange} |
| case ok: |
| return -1 * int64(abs), nil |
| } |
| } else { |
| // Prospectively parse a non-negative integer. |
| switch abs, ok := jsonwire.ParseUint(buf); { |
| case abs > +maxInt64: |
| return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange} |
| case ok: |
| return +1 * int64(abs), nil |
| } |
| } |
| // This is not a signed integer, which implies ErrSyntax. |
| if Kind(buf[0]).normalize() == '0' { |
| f64, _ := strconv.ParseFloat(string(buf), 64) |
| return f64toi64(f64), &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax} |
| } |
| } else if t.num != 0 { |
| // Handle typed Go number value. |
| switch t.str[0] { |
| case 'i': |
| return int64(t.num), nil |
| case 'u': |
| if t.num > maxInt64 { |
| return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange} |
| } |
| return int64(t.num), nil |
| case 'f', 'F': |
| f64 := float64(math.Float64frombits(uint64(t.num))) |
| if t.str[0] == 'F' { |
| f64 = float64(math.Float32frombits(uint32(t.num))) |
| } |
| switch i64 := f64toi64(f64); { |
| case math.IsNaN(f64), math.Trunc(f64) != f64: |
| return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax} |
| case (i64 == minInt64 && f64 < minInt64) || (i64 == maxInt64 && f64 > maxInt64): |
| return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange} |
| default: |
| return i64, nil |
| } |
| } |
| } |
| |
| panic("invalid JSON token kind: " + t.Kind().String()) |
| } |
| |
| func f64toi64(f64 float64) int64 { |
| switch { |
| case math.IsNaN(f64): |
| return 0 |
| case f64 >= maxInt64+1: |
| return maxInt64 |
| case f64 < minInt64: |
| return minInt64 |
| default: |
| return int64(f64) // NOTE: This may lead to loss of precision. |
| } |
| } |
| |
| // Uint returns the unsigned integer value for a JSON number. |
| // |
| // It reports an error that matches [strconv.ErrSyntax] if the JSON number |
| // does not match the restricted grammar of just an unsigned integer. |
| // It reports an error that matches [strconv.ErrRange] if the JSON number |
| // is an unsigned integer, but outside the representable range of an uint64. |
| // Even if an error is reported, a reasonable value is still returned. |
| // The fractional component of any number is ignored (truncation toward zero). |
| // Any number beyond the representation of an uint64 will be saturated |
| // to the closest representable value. |
| // |
| // It panics if the token kind is not a JSON number. |
| func (t Token) Uint() (uint64, error) { |
| // NOTE: This accessor returns 0 for any negative JSON number, |
| // which might be surprising, but is at least consistent with the behavior |
| // of saturating out-of-bounds numbers to the closest representable number. |
| // We report ErrSyntax instead of ErrRange since the grammar for |
| // an unsigned integer does not permit a negative sign. |
| |
| if raw := t.raw; raw != nil { |
| // Handle raw JSON number value. |
| if uint64(raw.previousOffsetStart()) != t.num { |
| panic(invalidTokenPanic) |
| } |
| buf := raw.previousBuffer() |
| // Prospectively parse an unsigned integer. |
| switch abs, ok := jsonwire.ParseUint(buf); { |
| case ok: |
| return abs, nil |
| case abs == maxUint64: // implies overflows |
| return maxUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange} |
| } |
| // This is not an unsigned integer, which implies ErrSyntax. |
| if Kind(buf[0]).normalize() == '0' { |
| f64, _ := strconv.ParseFloat(string(buf), 64) |
| return f64tou64(f64), &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax} |
| } |
| } else if t.num != 0 { |
| // Handle typed Go number value. |
| switch t.str[0] { |
| case 'u': |
| return t.num, nil |
| case 'i': |
| if int64(t.num) < minUint64 { |
| return minUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax} |
| } |
| return uint64(int64(t.num)), nil |
| case 'f', 'F': |
| f64 := float64(math.Float64frombits(uint64(t.num))) |
| if t.str[0] == 'F' { |
| f64 = float64(math.Float32frombits(uint32(t.num))) |
| } |
| switch u64 := f64tou64(f64); { |
| case math.IsNaN(f64), math.Trunc(f64) != f64, math.Signbit(f64): |
| return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax} |
| case (u64 == minUint64 && f64 < minUint64) || (u64 == maxUint64 && f64 > maxUint64): |
| return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange} |
| default: |
| return u64, nil |
| } |
| } |
| } |
| |
| panic("invalid JSON token kind: " + t.Kind().String()) |
| } |
| |
| func f64tou64(f64 float64) uint64 { |
| switch { |
| case math.IsNaN(f64): |
| return 0 |
| case f64 >= maxUint64+1: |
| return maxUint64 |
| case f64 < minUint64: |
| return minUint64 |
| default: |
| return uint64(f64) // NOTE: This may lead to loss of precision. |
| } |
| } |
| |
| // Kind returns the token kind. |
| func (t Token) Kind() Kind { |
| switch { |
| case t.raw != nil: |
| raw := t.raw |
| if uint64(raw.previousOffsetStart()) != t.num { |
| panic(invalidTokenPanic) |
| } |
| return Kind(t.raw.buf[raw.prevStart]).normalize() |
| case t.num != 0: |
| return '0' |
| case len(t.str) != 0: |
| return '"' |
| default: |
| return invalidKind |
| } |
| } |
| |
| // A Kind represents the kind of a JSON token. |
| // |
| // Kind represents each possible JSON token kind with a single byte, |
| // which is conveniently the first byte of that kind's grammar |
| // with the restriction that numbers always be represented with '0'. |
| type Kind byte |
| |
| const ( |
| KindInvalid Kind = 0 // invalid kind |
| KindNull Kind = 'n' // null |
| KindFalse Kind = 'f' // false |
| KindTrue Kind = 't' // true |
| KindString Kind = '"' // string |
| KindNumber Kind = '0' // number |
| KindBeginObject Kind = '{' // begin object |
| KindEndObject Kind = '}' // end object |
| KindBeginArray Kind = '[' // begin array |
| KindEndArray Kind = ']' // end array |
| ) |
| |
| const invalidKind Kind = 0 |
| |
| // String prints the kind in a humanly readable fashion. |
| func (k Kind) String() string { |
| switch k { |
| case 0: |
| return "invalid" |
| case 'n': |
| return "null" |
| case 'f': |
| return "false" |
| case 't': |
| return "true" |
| case '"': |
| return "string" |
| case '0': |
| return "number" |
| case '{': |
| return "{" |
| case '}': |
| return "}" |
| case '[': |
| return "[" |
| case ']': |
| return "]" |
| default: |
| return "<invalid jsontext.Kind: " + jsonwire.QuoteRune([]byte{byte(k)}) + ">" |
| } |
| } |
| |
| var normKind = [256]Kind{ |
| 'n': 'n', |
| 'f': 'f', |
| 't': 't', |
| '"': '"', |
| '{': '{', |
| '}': '}', |
| '[': '[', |
| ']': ']', |
| '-': '0', |
| '0': '0', |
| '1': '0', |
| '2': '0', |
| '3': '0', |
| '4': '0', |
| '5': '0', |
| '6': '0', |
| '7': '0', |
| '8': '0', |
| '9': '0', |
| } |
| |
| // normalize coalesces all possible starting characters of a number as just '0', |
| // and converts all invalid kinds to 0. |
| func (k Kind) normalize() Kind { |
| // A lookup table keeps the inlining cost as low as possible. |
| return normKind[k] |
| } |