// 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 start or end delimiter for a JSON object (i.e., { or } )// - a start 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.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 (float) ║ 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", "i", or "u" if num should be interpreted // as a float64, int64, or uint64, respectively. str string// num is a float64, int64, or uint64 stored as a uint64 value. // 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: }}// Float constructs a Token representing a JSON number.// 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 makes a copy of the Token such that its value remains valid// even after a subsequent [Decoder.Read] call.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 } }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(, .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, math.Float64frombits(.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(, math.Float64frombits(.num), 64), nilcase'i':returnstrconv.AppendInt(, int64(.num), 10), nilcase'u':returnstrconv.AppendUint(, uint64(.num), 10), nil } }panic("invalid JSON token kind: " + .Kind().String())}// Float returns the floating-point value for a JSON number.// It returns a NaN, +Inf, or -Inf value for any JSON string// with the values "NaN", "Infinity", or "-Infinity".// It panics for all other cases.func ( Token) () float64 {if := .raw; != nil {// Handle raw number value.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := .previousBuffer()ifKind([0]).normalize() == '0' { , := jsonwire.ParseFloat(, 64)return } } elseif .num != 0 {// Handle exact number value.switch .str[0] {case'f':returnmath.Float64frombits(.num)case'i':returnfloat64(int64(.num))case'u':returnfloat64(uint64(.num)) } }// Handle string values with "NaN", "Infinity", or "-Infinity".if .Kind() == '"' {switch .String() {case"NaN":returnmath.NaN()case"Infinity":returnmath.Inf(+1)case"-Infinity":returnmath.Inf(-1) } }panic("invalid JSON token kind: " + .Kind().String())}// Int returns the signed integer value for a JSON number.// 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 {if := .raw; != nil {// Handle raw integer value.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := false := .previousBuffer()iflen() > 0 && [0] == '-' { , = true, [1:] }if , := jsonwire.ParseUint(); {if {if > -minInt64 {returnminInt64 }return -1 * int64() } else {if > +maxInt64 {returnmaxInt64 }return +1 * int64() } } } elseif .num != 0 {// Handle exact integer value.switch .str[0] {case'i':returnint64(.num)case'u':if .num > maxInt64 {returnmaxInt64 }returnint64(.num) } }// Handle JSON number that is a floating-point value.if .Kind() == '0' {switch := .Float(); {case >= maxInt64:returnmaxInt64case <= minInt64:returnminInt64default:returnint64() // truncation toward zero } }panic("invalid JSON token kind: " + .Kind().String())}// Uint returns the unsigned integer value for a JSON number.// 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 ( Token) () uint64 {// 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.if := .raw; != nil {// Handle raw integer value.ifuint64(.previousOffsetStart()) != .num {panic(invalidTokenPanic) } := false := .previousBuffer()iflen() > 0 && [0] == '-' { , = true, [1:] }if , := jsonwire.ParseUint(); {if {returnminUint64 }return } } elseif .num != 0 {// Handle exact integer value.switch .str[0] {case'u':return .numcase'i':ifint64(.num) < minUint64 {returnminUint64 }returnuint64(int64(.num)) } }// Handle JSON number that is a floating-point value.if .Kind() == '0' {switch := .Float(); {case >= maxUint64:returnmaxUint64case <= minUint64:returnminUint64default:returnuint64() // truncation toward zero } }panic("invalid JSON token kind: " + .Kind().String())}// 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 }}// 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'://// - 'n': null// - 'f': false// - 't': true// - '"': string// - '0': number// - '{': object start// - '}': object end// - '[': array start// - ']': array end//// An invalid kind is usually represented using 0,// but may be non-zero due to invalid JSON data.typeKindbyteconst invalidKind Kind = 0// String prints the kind in a humanly readable fashion.func ( Kind) () string {switch {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(string()) + ">" }}// normalize coalesces all possible starting characters of a number as just '0'.func ( Kind) () Kind {if == '-' || ('0' <= && <= '9') {return'0' }return}
The pages are generated with Goldsv0.7.7-preview. (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.