// 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.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.typeTokenstruct {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 (NullToken = rawToken("null")FalseToken = rawToken("false")TrueToken = rawToken("true")BeginObjectToken = rawToken("{")EndObjectToken = rawToken("}")BeginArrayToken = rawToken("[")EndArrayToken = rawToken("]") zeroString Token = rawToken(`""`) zeroNumber Token = rawToken(`0`) nanString Token = String("NaN") pinfString Token = String("Infinity") ninfString Token = String("-Infinity"))func rawToken( string) Token {returnToken{raw: &decodeBuffer{buf: []byte(), prevStart: 0, prevEnd: len()}}}// Bool constructs a Token representing a JSON boolean.func ( bool) Token {if {returnTrue }returnFalse}// 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 {iflen() == 0 {returnzeroString }returnToken{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) {returnToken{str: "F", num: uint64(math.Float32bits())} }returnFloat(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 {casemath.Float64bits() == 0:returnzeroNumbercasemath.IsNaN():returnnanStringcasemath.IsInf(, +1):returnpinfStringcasemath.IsInf(, -1):returnninfString }returnToken{str: "f", num: math.Float64bits()}}// Int constructs a Token representing a JSON number from an int64.func ( int64) Token {if == 0 {returnzeroNumber }returnToken{str: "i", num: uint64()}}// Uint constructs a Token representing a JSON number from a uint64.func ( uint64) Token {if == 0 {returnzeroNumber }returnToken{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 {caseNull.raw:returnNullcaseFalse.raw:returnFalsecaseTrue.raw:returnTruecaseBeginObject.raw:returnBeginObjectcaseEndObject.raw:returnEndObjectcaseBeginArray.raw:returnBeginArraycaseEndArray.raw:returnEndArray } }// This only ever needs to clone strings and numbers.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := bytes.Clone(.previousBuffer())returnToken{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 {caseTrue.raw:returntruecaseFalse.raw:returnfalsedefault: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()ifKind([0]) == '"' {ifjsonwire.ConsumeSimpleString() == len() {returnappend(, ...), nil } , , := jsonwire.ReformatString(, , )return , } } elseiflen(.str) != 0 && .num == 0 {// Handle exact string value.returnjsonwire.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()iflen() > 0 {returnstring() }return}func ( Token) () (string, []byte) {if := .raw; != nil {ifuint64(.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"", }iflen(.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':returnstring(jsonwire.AppendFloat(nil, float64(math.Float32frombits(uint32(.num))), 32)), nilcase'f':returnstring(jsonwire.AppendFloat(nil, float64(math.Float64frombits(uint64(.num))), 64)), nilcase'i':returnstrconv.FormatInt(int64(.num), 10), nilcase'u':returnstrconv.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()ifKind([0]).normalize() == '0' { , , := jsonwire.ReformatNumber(, , )return , } } elseif .num != 0 {// Handle exact number value.switch .str[0] {case'F':returnjsonwire.AppendFloat(, float64(math.Float32frombits(uint32(.num))), 32), nilcase'f':returnjsonwire.AppendFloat(, float64(math.Float64frombits(uint64(.num))), 64), nilcase'i':returnstrconv.AppendInt(, int64(.num), 10), nilcase'u':returnstrconv.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)returnfloat32(), }// 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)returnfloat64(), }func ( Token) ( int) (float64, error) {if := .raw; != nil {// Handle raw JSON number value.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := .previousBuffer()ifKind([0]).normalize() == '0' { , := strconv.ParseFloat(string(), )if != nil { = &numError{accessor: "Float", value: .String(), err: errors.Unwrap()} // only ever ErrRange }return , } } elseif .num != 0 {// Handle typed Go number value.switch .str[0] {case'F':returnfloat64(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':returnfloat64(int64(.num)), nil// NOTE: This may lead to loss of precision.case'u':returnfloat64(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":returnmath.NaN(), nilcase"Infinity":returnmath.Inf(+1), nilcase"-Infinity":returnmath.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.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := .previousBuffer()iflen() > 0 && [0] == '-' {// Prospectively parse a negative integer.switch , := jsonwire.ParseUint([len("-"):]); {case > -minInt64:returnminInt64, &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:returnmaxInt64, &numError{accessor: "Int", value: .String(), err: strconv.ErrRange}case :return +1 * int64(), nil } }// This is not a signed integer, which implies ErrSyntax.ifKind([0]).normalize() == '0' { , := strconv.ParseFloat(string(), 64)returnf64toi64(), &numError{accessor: "Int", value: .String(), err: strconv.ErrSyntax} } } elseif .num != 0 {// Handle typed Go number value.switch .str[0] {case'i':returnint64(.num), nilcase'u':if .num > maxInt64 {returnmaxInt64, &numError{accessor: "Int", value: .String(), err: strconv.ErrRange} }returnint64(.num), nilcase'f', 'F': := float64(math.Float64frombits(uint64(.num)))if .str[0] == 'F' { = float64(math.Float32frombits(uint32(.num))) }switch := f64toi64(); {casemath.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 f64toi64( float64) int64 {switch {casemath.IsNaN():return0case >= maxInt64+1:returnmaxInt64case < minInt64:returnminInt64default:returnint64() // 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.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := .previousBuffer()// Prospectively parse an unsigned integer.switch , := jsonwire.ParseUint(); {case :return , nilcase == maxUint64: // implies overflowsreturnmaxUint64, &numError{accessor: "Uint", value: .String(), err: strconv.ErrRange} }// This is not an unsigned integer, which implies ErrSyntax.ifKind([0]).normalize() == '0' { , := strconv.ParseFloat(string(), 64)returnf64tou64(), &numError{accessor: "Uint", value: .String(), err: strconv.ErrSyntax} } } elseif .num != 0 {// Handle typed Go number value.switch .str[0] {case'u':return .num, nilcase'i':ifint64(.num) < minUint64 {returnminUint64, &numError{accessor: "Uint", value: .String(), err: strconv.ErrSyntax} }returnuint64(int64(.num)), nilcase'f', 'F': := float64(math.Float64frombits(uint64(.num)))if .str[0] == 'F' { = float64(math.Float32frombits(uint32(.num))) }switch := f64tou64(); {casemath.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 f64tou64( float64) uint64 {switch {casemath.IsNaN():return0case >= maxUint64+1:returnmaxUint64case < minUint64:returnminUint64default:returnuint64() // NOTE: This may lead to loss of precision. }}// Kind returns the token kind.func ( Token) () Kind {switch {case .raw != nil: := .rawifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) }returnKind(.raw.buf[.prevStart]).normalize()case .num != 0:return'0'caselen(.str) != 0:return'"'default:returninvalidKind }}// 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').typeKindbyteconst (KindInvalidKind = 0// invalid kindKindNullKind = 'n'// nullKindFalseKind = 'f'// falseKindTrueKind = 't'// trueKindStringKind = '"'// stringKindNumberKind = '0'// numberKindBeginObjectKind = '{'// begin objectKindEndObjectKind = '}'// end objectKindBeginArrayKind = '['// begin arrayKindEndArrayKind = ']'// end array)const invalidKind Kind = 0// String returns a string representation of k.func ( Kind) () string {switch {case0: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.returnnormKind[]}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.