// 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 ()// Encoder is a streaming encoder from raw JSON tokens and values.// It is used to write a stream of top-level JSON values,// each terminated with a newline character.//// [Encoder.WriteToken] and [Encoder.WriteValue] calls may be interleaved.// For example, the following JSON value://// {"name":"value","array":[null,false,true,3.14159],"object":{"k":"v"}}//// can be composed with the following calls (ignoring errors for brevity)://// e.WriteToken(BeginObject) // {// e.WriteToken(String("name")) // "name"// e.WriteToken(String("value")) // "value"// e.WriteValue(Value(`"array"`)) // "array"// e.WriteToken(BeginArray) // [// e.WriteToken(Null) // null// e.WriteToken(False) // false// e.WriteValue(Value("true")) // true// e.WriteToken(Float(3.14159)) // 3.14159// e.WriteToken(EndArray) // ]// e.WriteValue(Value(`"object"`)) // "object"// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}// e.WriteToken(EndObject) // }//// The above is one of many possible sequence of calls and// may not represent the most sensible method to call for any given token/value.// For example, it is probably more common to call [Encoder.WriteToken] with a string// for object names.typeEncoderstruct { s encoderState}// encoderState is the low-level state of Encoder.// It has exported fields and method for use by the "json" package.type encoderState struct {stateencodeBufferjsonopts.Struct SeenPointers map[any]struct{} // only used when marshaling; identical to json.seenPointers}// encodeBuffer is a buffer split into 2 segments://// - buf[0:len(buf)] // written (but unflushed) portion of the buffer// - buf[len(buf):cap(buf)] // unused portion of the buffertype encodeBuffer struct { Buf []byte// may alias wr if it is a bytes.Buffer// baseOffset is added to len(buf) to obtain the absolute offset // relative to the start of io.Writer stream. baseOffset int64 wr io.Writer// maxValue is the approximate maximum Value size passed to WriteValue. maxValue int// unusedCache is the buffer returned by the UnusedBuffer method. unusedCache []byte// bufStats is statistics about buffer utilization. // It is only used with pooled encoders in pools.go. bufStats bufferStatistics}// NewEncoder constructs a new streaming encoder writing to w// configured with the provided options.// It flushes the internal buffer when the buffer is sufficiently full or// when a top-level value has been written.//// If w is a [bytes.Buffer], then the encoder appends directly into the buffer// without copying the contents from an intermediate buffer.func ( io.Writer, ...Options) *Encoder { := new(Encoder) .Reset(, ...)return}// Reset resets an encoder such that it is writing afresh to w and// configured with the provided options. Reset must not be called on// a Encoder passed to the [encoding/json/v2.MarshalerTo.MarshalJSONTo] method// or the [encoding/json/v2.MarshalToFunc] function.func ( *Encoder) ( io.Writer, ...Options) {switch {case == nil:panic("jsontext: invalid nil Encoder")case == nil:panic("jsontext: invalid nil io.Writer")case .s.Flags.Get(jsonflags.WithinArshalCall):panic("jsontext: cannot reset Encoder passed to json.MarshalerTo") } .s.reset(nil, , ...)}func ( *encoderState) ( []byte, io.Writer, ...Options) { .state.reset() .encodeBuffer = encodeBuffer{Buf: , wr: , bufStats: .bufStats}if , := .(*bytes.Buffer); && != nil { .Buf = .Bytes()[.Len():] // alias the unused buffer of bb } := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts .Join(...) .Struct = if .Flags.Get(jsonflags.Multiline) {if !.Flags.Has(jsonflags.SpaceAfterColon) { .Flags.Set(jsonflags.SpaceAfterColon | 1) }if !.Flags.Has(jsonflags.SpaceAfterComma) { .Flags.Set(jsonflags.SpaceAfterComma | 0) }if !.Flags.Has(jsonflags.Indent) { .Flags.Set(jsonflags.Indent | 1) .Indent = "\t" } }}// Options returns the options used to construct the decoder and// may additionally contain semantic options passed to a// [encoding/json/v2.MarshalEncode] call.//// If operating within// a [encoding/json/v2.MarshalerTo.MarshalJSONTo] method call or// a [encoding/json/v2.MarshalToFunc] function call,// then the returned options are only valid within the call.func ( *Encoder) () Options {return &.s.Struct}// NeedFlush determines whether to flush at this point.func ( *encoderState) () bool {// NOTE: This function is carefully written to be inlinable.// Avoid flushing if e.wr is nil since there is no underlying writer. // Flush if less than 25% of the capacity remains. // Flushing at some constant fraction ensures that the buffer stops growing // so long as the largest Token or Value fits within that unused capacity.return .wr != nil && (.Tokens.Depth() == 1 || len(.Buf) > 3*cap(.Buf)/4)}// Flush flushes the buffer to the underlying io.Writer.// It may append a trailing newline after the top-level value.func ( *encoderState) () error {if .wr == nil || .avoidFlush() {returnnil }// In streaming mode, always emit a newline after the top-level value.if .Tokens.Depth() == 1 && !.Flags.Get(jsonflags.OmitTopLevelNewline) { .Buf = append(.Buf, '\n') }// Inform objectNameStack that we are about to flush the buffer content. .Names.copyQuotedBuffer(.Buf)// Specialize bytes.Buffer for better performance.if , := .wr.(*bytes.Buffer); {// If e.buf already aliases the internal buffer of bb, // then the Write call simply increments the internal offset, // otherwise Write operates as expected. // See https://go.dev/issue/42986. , := .Write(.Buf) // never fails unless bb is nil .baseOffset += int64()// If the internal buffer of bytes.Buffer is too small, // append operations elsewhere in the Encoder may grow the buffer. // This would be semantically correct, but hurts performance. // As such, ensure 25% of the current length is always available // to reduce the probability that other appends must allocate.if := .Available(); < .Len()/4 { .Grow( + 1) } .Buf = .AvailableBuffer()returnnil }// Flush the internal buffer to the underlying io.Writer. , := .wr.Write(.Buf) .baseOffset += int64()if != nil {// In the event of an error, preserve the unflushed portion. // Thus, write errors aren't fatal so long as the io.Writer // maintains consistent state after errors.if > 0 { .Buf = .Buf[:copy(.Buf, .Buf[:])] }return &ioError{action: "write", err: } } .Buf = .Buf[:0]// Check whether to grow the buffer. // Note that cap(e.buf) may already exceed maxBufferSize since // an append elsewhere already grew it to store a large token.const = 4 << 10const = 2// higher value is fasterconst = 2// higher value is slower// By default, grow if below the maximum buffer size. := cap(.Buf) <= /// Growing can be expensive, so only grow // if a sufficient number of bytes have been processed. = && int64(cap(.Buf)) < .previousOffsetEnd()/if { .Buf = make([]byte, 0, cap(.Buf)*) }returnnil}func ( *encodeBuffer) ( int) int64 { return .baseOffset + int64() }func ( *encodeBuffer) () int64 { return .baseOffset + int64(len(.Buf)) }func ( *encodeBuffer) () []byte { return .Buf }// avoidFlush indicates whether to avoid flushing to ensure there is always// enough in the buffer to unwrite the last object member if it were empty.func ( *encoderState) () bool {switch {case .Tokens.Last.Length() == 0:// Never flush after BeginObject or BeginArray since we don't know yet // if the object or array will end up being empty.returntruecase .Tokens.Last.needObjectValue():// Never flush before the object value since we don't know yet // if the object value will end up being empty.returntruecase .Tokens.Last.NeedObjectName() && len(.Buf) >= 2:// Never flush after the object value if it does turn out to be empty.switchstring(.Buf[len(.Buf)-2:]) {case`ll`, `""`, `{}`, `[]`: // last two bytes of every empty valuereturntrue } }returnfalse}// UnwriteEmptyObjectMember unwrites the last object member if it is empty// and reports whether it performed an unwrite operation.func ( *encoderState) ( *string) bool {if := .Tokens.Last; !.isObject() || !.NeedObjectName() || .Length() == 0 {panic("BUG: must be called on an object after writing a value") }// The flushing logic is modified to never flush a trailing empty value. // The encoder never writes trailing whitespace eagerly. := .unflushedBuffer()// Detect whether the last value was empty.varintiflen() >= 3 {switchstring([len()-2:]) {case"ll": // last two bytes of `null` = len(`null`)case`""`:// It is possible for a non-empty string to have `""` as a suffix // if the second to the last quote was escaped.if [len()-3] == '\\' {returnfalse// e.g., `"\""` is not empty } = len(`""`)case`{}`: = len(`{}`)case`[]`: = len(`[]`) } }if == 0 {returnfalse }// Unwrite the value, whitespace, colon, name, whitespace, and comma. = [:len()-] = jsonwire.TrimSuffixWhitespace() = jsonwire.TrimSuffixByte(, ':') = jsonwire.TrimSuffixString() = jsonwire.TrimSuffixWhitespace() = jsonwire.TrimSuffixByte(, ',') .Buf = // store back truncated unflushed buffer// Undo state changes. .Tokens.Last.decrement() // for object member value .Tokens.Last.decrement() // for object member nameif !.Flags.Get(jsonflags.AllowDuplicateNames) {if .Tokens.Last.isActiveNamespace() { .Namespaces.Last().removeLast() } } .Names.clearLast()if != nil { .Names.copyQuotedBuffer(.Buf) // required by objectNameStack.replaceLastUnquotedName .Names.replaceLastUnquotedName(*) }returntrue}// UnwriteOnlyObjectMemberName unwrites the only object member name// and returns the unquoted name.func ( *encoderState) () string {if := .Tokens.Last; !.isObject() || .Length() != 1 {panic("BUG: must be called on an object after writing first name") }// Unwrite the name and whitespace. := jsonwire.TrimSuffixString(.Buf) := bytes.IndexByte(.Buf[len():], '\\') < 0 := string(jsonwire.UnquoteMayCopy(.Buf[len():], )) .Buf = jsonwire.TrimSuffixWhitespace()// Undo state changes. .Tokens.Last.decrement()if !.Flags.Get(jsonflags.AllowDuplicateNames) {if .Tokens.Last.isActiveNamespace() { .Namespaces.Last().removeLast() } } .Names.clearLast()return}// WriteToken writes the next token and advances the internal write offset.//// The provided token kind must be consistent with the JSON grammar.// For example, it is an error to provide a number when the encoder// is expecting an object name (which is always a string), or// to provide an end object delimiter when the encoder is finishing an array.// If the provided token is invalid, then it reports a [SyntacticError] and// the internal state remains unchanged. The offset reported// in [SyntacticError] will be relative to the [Encoder.OutputOffset].func ( *Encoder) ( Token) error {return .s.WriteToken()}func ( *encoderState) ( Token) error { := .Kind() := .Buf// use local variable to avoid mutating e in case of error// Append any delimiters or optional whitespace. = .Tokens.MayAppendDelim(, )if .Flags.Get(jsonflags.AnyWhitespace) { = .appendWhitespace(, ) } := len() // offset before the token// Append the token to the output and to the state machine.varerrorswitch {case'n': = append(, "null"...) = .Tokens.appendLiteral()case'f': = append(, "false"...) = .Tokens.appendLiteral()case't': = append(, "true"...) = .Tokens.appendLiteral()case'"':if , = .appendString(, &.Flags); != nil {break }if .Tokens.Last.NeedObjectName() {if !.Flags.Get(jsonflags.AllowDuplicateNames) {if !.Tokens.Last.isValidNamespace() { = errInvalidNamespacebreak }if .Tokens.Last.isActiveNamespace() && !.Namespaces.Last().insertQuoted([:], false) { = wrapWithObjectName(ErrDuplicateName, [:])break } } .Names.ReplaceLastQuotedOffset() // only replace if insertQuoted succeeds } = .Tokens.appendString()case'0':if , = .appendNumber(, &.Flags); != nil {break } = .Tokens.appendNumber()case'{': = append(, '{')if = .Tokens.pushObject(); != nil {break } .Names.push()if !.Flags.Get(jsonflags.AllowDuplicateNames) { .Namespaces.push() }case'}': = append(, '}')if = .Tokens.popObject(); != nil {break } .Names.pop()if !.Flags.Get(jsonflags.AllowDuplicateNames) { .Namespaces.pop() }case'[': = append(, '[') = .Tokens.pushArray()case']': = append(, ']') = .Tokens.popArray()default: = errInvalidToken }if != nil {returnwrapSyntacticError(, , , +1) }// Finish off the buffer and store it back into e. .Buf = if .NeedFlush() {return .Flush() }returnnil}// AppendRaw appends either a raw string (without double quotes) or number.// Specify safeASCII if the string output is guaranteed to be ASCII// without any characters (including '<', '>', and '&') that need escaping,// otherwise this will validate whether the string needs escaping.// The appended bytes for a JSON number must be valid.//// This is a specialized implementation of Encoder.WriteValue// that allows appending directly into the buffer.// It is only called from marshal logic in the "json" package.func ( *encoderState) ( Kind, bool, func([]byte) ([]byte, error)) error { := .Buf// use local variable to avoid mutating e in case of error// Append any delimiters or optional whitespace. = .Tokens.MayAppendDelim(, )if .Flags.Get(jsonflags.AnyWhitespace) { = .appendWhitespace(, ) } := len() // offset before the tokenvarerrorswitch {case'"':// Append directly into the encoder buffer by assuming that // most of the time none of the characters need escaping. = append(, '"')if , = (); != nil {return } = append(, '"')// Check whether we need to escape the string and if necessary // copy it to a scratch buffer and then escape it back. := || !jsonwire.NeedEscape([+len(`"`):len()-len(`"`)])if ! {varerror := append(.unusedCache, [+len(`"`):len()-len(`"`)]...) , = jsonwire.AppendQuote([:], string(), &.Flags) .unusedCache = [:0]if != nil {returnwrapSyntacticError(, , , +1) } }// Update the state machine.if .Tokens.Last.NeedObjectName() {if !.Flags.Get(jsonflags.AllowDuplicateNames) {if !.Tokens.Last.isValidNamespace() {returnwrapSyntacticError(, , , +1) }if .Tokens.Last.isActiveNamespace() && !.Namespaces.Last().insertQuoted([:], ) { = wrapWithObjectName(ErrDuplicateName, [:])returnwrapSyntacticError(, , , +1) } } .Names.ReplaceLastQuotedOffset() // only replace if insertQuoted succeeds }if := .Tokens.appendString(); != nil {returnwrapSyntacticError(, , , +1) }case'0':if , = (); != nil {return }if := .Tokens.appendNumber(); != nil {returnwrapSyntacticError(, , , +1) }default:panic("BUG: invalid kind") }// Finish off the buffer and store it back into e. .Buf = if .NeedFlush() {return .Flush() }returnnil}// WriteValue writes the next raw value and advances the internal write offset.// The Encoder does not simply copy the provided value verbatim, but// parses it to ensure that it is syntactically valid and reformats it// according to how the Encoder is configured to format whitespace and strings.// If [AllowInvalidUTF8] is specified, then any invalid UTF-8 is mangled// as the Unicode replacement character, U+FFFD.//// The provided value kind must be consistent with the JSON grammar// (see examples on [Encoder.WriteToken]). If the provided value is invalid,// then it reports a [SyntacticError] and the internal state remains unchanged.// The offset reported in [SyntacticError] will be relative to the// [Encoder.OutputOffset] plus the offset into v of any encountered syntax error.func ( *Encoder) ( Value) error {return .s.WriteValue()}func ( *encoderState) ( Value) error { .maxValue |= len() // bitwise OR is a fast approximation of max := .Kind() := .Buf// use local variable to avoid mutating e in case of error// Append any delimiters or optional whitespace. = .Tokens.MayAppendDelim(, )if .Flags.Get(jsonflags.AnyWhitespace) { = .appendWhitespace(, ) } := len() // offset before the value// Append the value the output.varint += jsonwire.ConsumeWhitespace([:]) , , := .reformatValue(, [:], .Tokens.Depth())if != nil {returnwrapSyntacticError(, , ++, +1) } += += jsonwire.ConsumeWhitespace([:])iflen() > { = jsonwire.NewInvalidCharacterError([:], "after top-level value")returnwrapSyntacticError(, , +, 0) }// Append the kind to the state machine.switch {case'n', 'f', 't': = .Tokens.appendLiteral()case'"':if .Tokens.Last.NeedObjectName() {if !.Flags.Get(jsonflags.AllowDuplicateNames) {if !.Tokens.Last.isValidNamespace() { = errInvalidNamespacebreak }if .Tokens.Last.isActiveNamespace() && !.Namespaces.Last().insertQuoted([:], false) { = wrapWithObjectName(ErrDuplicateName, [:])break } } .Names.ReplaceLastQuotedOffset() // only replace if insertQuoted succeeds } = .Tokens.appendString()case'0': = .Tokens.appendNumber()case'{':if = .Tokens.pushObject(); != nil {break }if = .Tokens.popObject(); != nil {panic("BUG: popObject should never fail immediately after pushObject: " + .Error()) }if .Flags.Get(jsonflags.ReorderRawObjects) {mustReorderObjects([:]) }case'[':if = .Tokens.pushArray(); != nil {break }if = .Tokens.popArray(); != nil {panic("BUG: popArray should never fail immediately after pushArray: " + .Error()) }if .Flags.Get(jsonflags.ReorderRawObjects) {mustReorderObjects([:]) } }if != nil {returnwrapSyntacticError(, , , +1) }// Finish off the buffer and store it back into e. .Buf = if .NeedFlush() {return .Flush() }returnnil}// CountNextDelimWhitespace counts the number of bytes of delimiter and// whitespace bytes assuming the upcoming token is a JSON value.// This method is used for error reporting at the semantic layer.func ( *encoderState) () ( int) {const = Kind('"') // arbitrary kind as next JSON value := .Tokens.needDelim()if > 0 { += len(",") | len(":") }if == ':' {if .Flags.Get(jsonflags.SpaceAfterColon) { += len(" ") } } else {if == ',' && .Flags.Get(jsonflags.SpaceAfterComma) { += len(" ") }if .Flags.Get(jsonflags.Multiline) {if := .Tokens.NeedIndent(); > 0 { += len("\n") + len(.IndentPrefix) + (-1)*len(.Indent) } } }return}// appendWhitespace appends whitespace that immediately precedes the next token.func ( *encoderState) ( []byte, Kind) []byte {if := .Tokens.needDelim(); == ':' {if .Flags.Get(jsonflags.SpaceAfterColon) { = append(, ' ') } } else {if == ',' && .Flags.Get(jsonflags.SpaceAfterComma) { = append(, ' ') }if .Flags.Get(jsonflags.Multiline) { = .AppendIndent(, .Tokens.NeedIndent()) } }return}// AppendIndent appends the appropriate number of indentation characters// for the current nested level, n.func ( *encoderState) ( []byte, int) []byte {if == 0 {return } = append(, '\n') = append(, .IndentPrefix...)for ; > 1; -- { = append(, .Indent...) }return}// reformatValue parses a JSON value from the start of src and// appends it to the end of dst, reformatting whitespace and strings as needed.// It returns the extended dst buffer and the number of consumed input bytes.func ( *encoderState) ( []byte, Value, int) ([]byte, int, error) {// TODO: Should this update ValueFlags as input?iflen() == 0 {return , 0, io.ErrUnexpectedEOF }switch := Kind([0]).normalize(); {case'n':ifjsonwire.ConsumeNull() == 0 { , := jsonwire.ConsumeLiteral(, "null")return , , }returnappend(, "null"...), len("null"), nilcase'f':ifjsonwire.ConsumeFalse() == 0 { , := jsonwire.ConsumeLiteral(, "false")return , , }returnappend(, "false"...), len("false"), nilcase't':ifjsonwire.ConsumeTrue() == 0 { , := jsonwire.ConsumeLiteral(, "true")return , , }returnappend(, "true"...), len("true"), nilcase'"':if := jsonwire.ConsumeSimpleString(); > 0 { = append(, [:]...) // copy simple strings verbatimreturn , , nil }returnjsonwire.ReformatString(, , &.Flags)case'0':if := jsonwire.ConsumeSimpleNumber(); > 0 && !.Flags.Get(jsonflags.CanonicalizeNumbers) { = append(, [:]...) // copy simple numbers verbatimreturn , , nil }returnjsonwire.ReformatNumber(, , &.Flags)case'{':return .reformatObject(, , )case'[':return .reformatArray(, , )default:return , 0, jsonwire.NewInvalidCharacterError(, "at start of value") }}// reformatObject parses a JSON object from the start of src and// appends it to the end of src, reformatting whitespace and strings as needed.// It returns the extended dst buffer and the number of consumed input bytes.func ( *encoderState) ( []byte, Value, int) ([]byte, int, error) {// Append object start.iflen() == 0 || [0] != '{' {panic("BUG: reformatObject must be called with a buffer that starts with '{'") } elseif == maxNestingDepth+1 {return , 0, errMaxDepth } = append(, '{') := len("{")// Append (possible) object end. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , io.ErrUnexpectedEOF }if [] == '}' { = append(, '}') += len("}")return , , nil }varerrorvar *objectNamespaceif !.Flags.Get(jsonflags.AllowDuplicateNames) { .Namespaces.push()defer .Namespaces.pop() = .Namespaces.Last() } ++for {// Append optional newline and indentation.if .Flags.Get(jsonflags.Multiline) { = .AppendIndent(, ) }// Append object name. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , io.ErrUnexpectedEOF } := jsonwire.ConsumeSimpleString([:]) := > 0if { = append(, [:+]...) } else { , , = jsonwire.ReformatString(, [:], &.Flags)if != nil {return , + , } } := [ : +]if !.Flags.Get(jsonflags.AllowDuplicateNames) && !.insertQuoted(, ) {return , , wrapWithObjectName(ErrDuplicateName, ) } += // Append colon. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , wrapWithObjectName(io.ErrUnexpectedEOF, ) }if [] != ':' { = jsonwire.NewInvalidCharacterError([:], "after object name (expecting ':')")return , , wrapWithObjectName(, ) } = append(, ':') += len(":")if .Flags.Get(jsonflags.SpaceAfterColon) { = append(, ' ') }// Append object value. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , wrapWithObjectName(io.ErrUnexpectedEOF, ) } , , = .reformatValue(, [:], )if != nil {return , + , wrapWithObjectName(, ) } += // Append comma or object end. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , io.ErrUnexpectedEOF }switch [] {case',': = append(, ',')if .Flags.Get(jsonflags.SpaceAfterComma) { = append(, ' ') } += len(",")continuecase'}':if .Flags.Get(jsonflags.Multiline) { = .AppendIndent(, -1) } = append(, '}') += len("}")return , , nildefault:return , , jsonwire.NewInvalidCharacterError([:], "after object value (expecting ',' or '}')") } }}// reformatArray parses a JSON array from the start of src and// appends it to the end of dst, reformatting whitespace and strings as needed.// It returns the extended dst buffer and the number of consumed input bytes.func ( *encoderState) ( []byte, Value, int) ([]byte, int, error) {// Append array start.iflen() == 0 || [0] != '[' {panic("BUG: reformatArray must be called with a buffer that starts with '['") } elseif == maxNestingDepth+1 {return , 0, errMaxDepth } = append(, '[') := len("[")// Append (possible) array end. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , io.ErrUnexpectedEOF }if [] == ']' { = append(, ']') += len("]")return , , nil }varint64varerror ++for {// Append optional newline and indentation.if .Flags.Get(jsonflags.Multiline) { = .AppendIndent(, ) }// Append array value. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , io.ErrUnexpectedEOF }varint , , = .reformatValue(, [:], )if != nil {return , + , wrapWithArrayIndex(, ) } += // Append comma or array end. += jsonwire.ConsumeWhitespace([:])ifuint(len()) <= uint() {return , , io.ErrUnexpectedEOF }switch [] {case',': = append(, ',')if .Flags.Get(jsonflags.SpaceAfterComma) { = append(, ' ') } += len(",") ++continuecase']':if .Flags.Get(jsonflags.Multiline) { = .AppendIndent(, -1) } = append(, ']') += len("]")return , , nildefault:return , , jsonwire.NewInvalidCharacterError([:], "after array value (expecting ',' or ']')") } }}// OutputOffset returns the current output byte offset. It gives the location// of the next byte immediately after the most recently written token or value.// The number of bytes actually written to the underlying [io.Writer] may be less// than this offset due to internal buffering effects.func ( *Encoder) () int64 {return .s.previousOffsetEnd()}// UnusedBuffer returns a zero-length buffer with a possible non-zero capacity.// This buffer is intended to be used to populate a [Value]// being passed to an immediately succeeding [Encoder.WriteValue] call.//// Example usage://// b := d.UnusedBuffer()// b = append(b, '"')// b = appendString(b, v) // append the string formatting of v// b = append(b, '"')// ... := d.WriteValue(b)//// It is the user's responsibility to ensure that the value is valid JSON.func ( *Encoder) () []byte {// NOTE: We don't return e.buf[len(e.buf):cap(e.buf)] since WriteValue would // need to take special care to avoid mangling the data while reformatting. // WriteValue can't easily identify whether the input Value aliases e.buf // without using unsafe.Pointer. Thus, we just return a different buffer. // Should this ever alias e.buf, we need to consider how it operates with // the specialized performance optimization for bytes.Buffer. := 1 << bits.Len(uint(.s.maxValue|63)) // fast approximation for max lengthifcap(.s.unusedCache) < { .s.unusedCache = make([]byte, 0, ) }return .s.unusedCache}// StackDepth returns the depth of the state machine for written JSON data.// Each level on the stack represents a nested JSON object or array.// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered// and decremented whenever an [EndObject] or [EndArray] token is encountered.// The depth is zero-indexed, where zero represents the top-level JSON value.func ( *Encoder) () int {// NOTE: Keep in sync with Decoder.StackDepth.return .s.Tokens.Depth() - 1}// StackIndex returns information about the specified stack level.// It must be a number between 0 and [Encoder.StackDepth], inclusive.// For each level, it reports the kind://// - 0 for a level of zero,// - '{' for a level representing a JSON object, and// - '[' for a level representing a JSON array.//// It also reports the length of that JSON object or array.// Each name and value in a JSON object is counted separately,// so the effective number of members would be half the length.// A complete JSON object must have an even length.func ( *Encoder) ( int) (Kind, int64) {// NOTE: Keep in sync with Decoder.StackIndex.switch := .s.Tokens.index(); {case > 0 && .isObject():return'{', .Length()case > 0 && .isArray():return'[', .Length()default:return0, .Length() }}// StackPointer returns a JSON Pointer (RFC 6901) to the most recently written value.func ( *Encoder) () Pointer {returnPointer(.s.AppendStackPointer(nil, -1))}func ( *encoderState) ( []byte, int) []byte { .Names.copyQuotedBuffer(.Buf)return .state.appendStackPointer(, )}
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.