Source File
token.go
Belonging Package
encoding/json/jsontext
// 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.jsonv2package jsontextimport ()// NOTE: Token is analogous to v1 json.Token.const (maxInt64 = math.MaxInt64minInt64 = math.MinInt64maxUint64 = math.MaxUint64minUint64 = 0 // for consistency and readability purposesinvalidTokenPanic = "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 ( string) Token {return Token{raw: &decodeBuffer{buf: []byte(), prevStart: 0, prevEnd: len()}}}// Bool constructs a Token representing a JSON boolean.func ( bool) Token {if {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) Token {if len() == 0 {return zeroString}return Token{str: }}// 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) Token {if != 0 && !math.IsNaN(float64()) && !math.IsInf(float64(), 0) {return Token{str: "F", num: uint64(math.Float32bits())}}return Float(float64()) // 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 ( float64) Token {switch {case math.Float64bits() == 0:return zeroNumbercase math.IsNaN():return nanStringcase math.IsInf(, +1):return pinfStringcase math.IsInf(, -1):return ninfString}return Token{str: "f", num: math.Float64bits()}}// Int constructs a Token representing a JSON number from an int64.func ( int64) Token {if == 0 {return zeroNumber}return Token{str: "i", num: uint64()}}// Uint constructs a Token representing a JSON number from a uint64.func ( uint64) Token {if == 0 {return zeroNumber}return Token{str: "u", num: uint64()}}// 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 ( Token) () Token {// TODO: Allow caller to avoid any allocations?if := .raw; != nil {// Avoid copying globals.if .raw.prevStart == 0 {switch .raw {case Null.raw:return Nullcase False.raw:return Falsecase True.raw:return Truecase BeginObject.raw:return BeginObjectcase EndObject.raw:return EndObjectcase BeginArray.raw:return BeginArraycase EndArray.raw:return EndArray}}// This only ever needs to clone strings and numbers.if uint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic)}:= bytes.Clone(.previousBuffer())return Token{raw: &decodeBuffer{buf: , prevStart: 0, prevEnd: len()}}}return}// Bool returns the value for a JSON boolean.// It panics if the token kind is not a JSON boolean.func ( Token) () bool {switch .raw {case True.raw:return truecase False.raw:return falsedefault:panic("invalid JSON token kind: " + .Kind().String())}}// appendString appends a JSON string to dst and returns it.// It panics if t is not a JSON string.func ( Token) ( []byte, *jsonflags.Flags) ([]byte, error) {if := .raw; != nil {// Handle raw string value.:= .previousBuffer()if Kind([0]) == '"' {if jsonwire.ConsumeSimpleString() == len() {return append(, ...), nil}, , := jsonwire.ReformatString(, , )return ,}} else if len(.str) != 0 && .num == 0 {// Handle exact string value.return jsonwire.AppendQuote(, []byte(.str), )}panic("invalid JSON token kind: " + .Kind().String())}// String returns the unescaped string value for a JSON string.// For other JSON kinds, this returns the raw JSON representation.func ( Token) () 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/, := .string()if len() > 0 {return string()}return}func ( Token) () (string, []byte) {if := .raw; != nil {if uint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic)}:= .previousBuffer()if [0] == '"' {// TODO: Preserve ValueFlags in Token?:= jsonwire.ConsumeSimpleString() == len()return "", jsonwire.UnquoteMayCopy(, )}// Handle tokens that are not JSON strings for fmt.Stringer.return "",}if len(.str) != 0 && .num == 0 {return .str, nil}// Handle tokens that are not JSON strings for fmt.Stringer.if .num > 0 {switch .str[0] {case 'F':return string(jsonwire.AppendFloat(nil, float64(math.Float32frombits(uint32(.num))), 32)), nilcase 'f':return string(jsonwire.AppendFloat(nil, float64(math.Float64frombits(uint64(.num))), 64)), nilcase 'i':return strconv.FormatInt(int64(.num), 10), nilcase 'u':return strconv.FormatUint(uint64(.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 ( Token) ( []byte, *jsonflags.Flags) ([]byte, error) {if := .raw; != nil {// Handle raw number value.:= .previousBuffer()if Kind([0]).normalize() == '0' {, , := jsonwire.ReformatNumber(, , )return ,}} else if .num != 0 {// Handle exact number value.switch .str[0] {case 'F':return jsonwire.AppendFloat(, float64(math.Float32frombits(uint32(.num))), 32), nilcase 'f':return jsonwire.AppendFloat(, float64(math.Float64frombits(uint64(.num))), 64), nilcase 'i':return strconv.AppendInt(, int64(.num), 10), nilcase 'u':return strconv.AppendUint(, uint64(.num), 10), nil}}panic("invalid JSON token kind: " + .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 ( Token) () (float32, error) {, := .float(32)return float32(),}// 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 ( Token) () (float64, error) {, := .float(64)return float64(),}func ( Token) ( int) (float64, error) {if := .raw; != nil {// Handle raw JSON number value.if uint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic)}:= .previousBuffer()if Kind([0]).normalize() == '0' {, := strconv.ParseFloat(string(), )if != nil {= &numError{accessor: "Float", value: .String(), err: errors.Unwrap()} // only ever ErrRange}return ,}} else if .num != 0 {// Handle typed Go number value.switch .str[0] {case 'F':return float64(math.Float32frombits(uint32(.num))), nilcase 'f'::= float64(math.Float64frombits(uint64(.num)))if == 32 && !math.IsInf(, 0) && math.IsInf(float64(float32()), 0) {return , &numError{accessor: "Float", value: .String(), err: strconv.ErrRange}}return , nilcase 'i':return float64(int64(.num)), nil // NOTE: This may lead to loss of precision.case 'u':return float64(uint64(.num)), nil // NOTE: This may lead to loss of precision.}}// Handle string values with "NaN", "Infinity", or "-Infinity".if .Kind() == '"' {switch .String() {case "NaN":return math.NaN(), nilcase "Infinity":return math.Inf(+1), nilcase "-Infinity":return math.Inf(-1), nil}// TODO: Should this be an error instead of a panic?// We can safely switch from a panic to an error in the future.}panic("invalid JSON token kind: " + .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 ( Token) () (int64, error) {if := .raw; != nil {// Handle raw JSON number value.if uint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic)}:= .previousBuffer()if len() > 0 && [0] == '-' {// Prospectively parse a negative integer.switch , := jsonwire.ParseUint([len("-"):]); {case > -minInt64:return minInt64, &numError{accessor: "Int", value: .String(), err: strconv.ErrRange}case :return -1 * int64(), nil}} else {// Prospectively parse a non-negative integer.switch , := jsonwire.ParseUint(); {case > +maxInt64:return maxInt64, &numError{accessor: "Int", value: .String(), err: strconv.ErrRange}case :return +1 * int64(), nil}}// This is not a signed integer, which implies ErrSyntax.if Kind([0]).normalize() == '0' {, := strconv.ParseFloat(string(), 64)return f64toi64(), &numError{accessor: "Int", value: .String(), err: strconv.ErrSyntax}}} else if .num != 0 {// Handle typed Go number value.switch .str[0] {case 'i':return int64(.num), nilcase 'u':if .num > maxInt64 {return maxInt64, &numError{accessor: "Int", value: .String(), err: strconv.ErrRange}}return int64(.num), nilcase 'f', 'F'::= float64(math.Float64frombits(uint64(.num)))if .str[0] == 'F' {= float64(math.Float32frombits(uint32(.num)))}switch := f64toi64(); {case math.IsNaN(), math.Trunc() != :return , &numError{accessor: "Int", value: .String(), err: strconv.ErrSyntax}case ( == minInt64 && < minInt64) || ( == maxInt64 && > maxInt64):return , &numError{accessor: "Int", value: .String(), err: strconv.ErrRange}default:return , nil}}}panic("invalid JSON token kind: " + .Kind().String())}func ( float64) int64 {switch {case math.IsNaN():return 0case >= maxInt64+1:return maxInt64case < minInt64:return minInt64default:return int64() // 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 a 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 a uint64 will be saturated// to the closest representable value.//// It panics if the token kind is not a JSON number.func ( Token) () (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; != nil {// Handle raw JSON number value.if uint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic)}:= .previousBuffer()// Prospectively parse an unsigned integer.switch , := jsonwire.ParseUint(); {case :return , nilcase == maxUint64: // implies overflowsreturn maxUint64, &numError{accessor: "Uint", value: .String(), err: strconv.ErrRange}}// This is not an unsigned integer, which implies ErrSyntax.if Kind([0]).normalize() == '0' {, := strconv.ParseFloat(string(), 64)return f64tou64(), &numError{accessor: "Uint", value: .String(), err: strconv.ErrSyntax}}} else if .num != 0 {// Handle typed Go number value.switch .str[0] {case 'u':return .num, nilcase 'i':if int64(.num) < minUint64 {return minUint64, &numError{accessor: "Uint", value: .String(), err: strconv.ErrSyntax}}return uint64(int64(.num)), nilcase 'f', 'F'::= float64(math.Float64frombits(uint64(.num)))if .str[0] == 'F' {= float64(math.Float32frombits(uint32(.num)))}switch := f64tou64(); {case math.IsNaN(), math.Trunc() != , math.Signbit():return , &numError{accessor: "Uint", value: .String(), err: strconv.ErrSyntax}case ( == minUint64 && < minUint64) || ( == maxUint64 && > maxUint64):return , &numError{accessor: "Uint", value: .String(), err: strconv.ErrRange}default:return , nil}}}panic("invalid JSON token kind: " + .Kind().String())}func ( float64) uint64 {switch {case math.IsNaN():return 0case >= maxUint64+1:return maxUint64case < minUint64:return minUint64default:return uint64() // NOTE: This may lead to loss of precision.}}// Kind returns the token kind.func ( Token) () Kind {switch {case .raw != nil::= .rawif uint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic)}return Kind(.raw.buf[.prevStart]).normalize()case .num != 0:return '0'case len(.str) != 0:return '"'default:return invalidKind}}// A Kind represents the kind of a JSON token.//// A Kind is a single byte, which is conveniently the first byte of that// kind's symbol in the grammar (except for numbers, which are always represented// with '0').type Kind byteconst (KindInvalid Kind = 0 // invalid kindKindNull Kind = 'n' // nullKindFalse Kind = 'f' // falseKindTrue Kind = 't' // trueKindString Kind = '"' // stringKindNumber Kind = '0' // numberKindBeginObject Kind = '{' // begin objectKindEndObject Kind = '}' // end objectKindBeginArray Kind = '[' // begin arrayKindEndArray Kind = ']' // end array)const invalidKind Kind = 0// String returns a string representation of k.func ( Kind) () string {switch {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()}) + ">"}}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 ( Kind) () Kind {// A lookup table keeps the inlining cost as low as possible.return normKind[]}
The pages are generated with Golds v0.8.4. (GOOS=linux GOARCH=amd64)