| // 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" | |
| "io" | |
| "math/bits" | |
| "encoding/json/internal/jsonflags" | |
| "encoding/json/internal/jsonopts" | |
| "encoding/json/internal/jsonwire" | |
| ) | |
| // Encoder is a streaming encoder to 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 sequences 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. | |
| type Encoder struct { | |
| s encoderState | |
| } | |
| // encoderState is the low-level state of Encoder. | |
| // It has exported fields and methods for use by the "json" package. | |
| type encoderState struct { | |
| state | |
| encodeBuffer | |
| jsonopts.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 buffer | |
| type 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 | |
| // availBuffer is the buffer returned by the AvailableBuffer method. | |
| availBuffer []byte // always has zero length | |
| // 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 NewEncoder(w io.Writer, opts ...Options) *Encoder { | |
| e := new(Encoder) | |
| e.Reset(w, opts...) | |
| return e | |
| } | |
| // Reset resets an encoder such that it is writing afresh to w and | |
| // configured with the provided options. Reset must not be called on | |
| // an Encoder passed to the [encoding/json/v2.MarshalerTo.MarshalJSONTo] method | |
| // or the [encoding/json/v2.MarshalToFunc] function. | |
| func (e *Encoder) Reset(w io.Writer, opts ...Options) { | |
| switch { | |
| case e == nil: | |
| panic("jsontext: invalid nil Encoder") | |
| case e.s.Flags.Get(jsonflags.WithinArshalCall): | |
| panic("jsontext: cannot reset Encoder passed to json.MarshalerTo") | |
| } | |
| // Reuse the buffer if it does not alias a previous [bytes.Buffer]. | |
| b := e.s.Buf[:0] | |
| if _, ok := e.s.wr.(*bytes.Buffer); ok { | |
| b = nil | |
| } | |
| e.s.reset(b, w, opts...) | |
| } | |
| func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) { | |
| e.state.reset() | |
| e.encodeBuffer = encodeBuffer{Buf: b, wr: w, availBuffer: e.availBuffer, bufStats: e.bufStats} | |
| if bb, ok := w.(*bytes.Buffer); ok && bb != nil { | |
| e.Buf = bb.AvailableBuffer() // alias the unused buffer of bb | |
| } | |
| opts2 := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts | |
| opts2.Join(opts...) | |
| e.Struct = opts2 | |
| if e.Struct.Flags.Get(jsonflags.Multiline) { | |
| e.Struct.InitializeMultiline() | |
| } | |
| } | |
| // Options returns the options used to construct the encoder 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 (e *Encoder) Options() Options { | |
| return &e.s.Struct | |
| } | |
| func (e *encoderState) options() *jsonopts.Struct { return &e.Struct } | |
| // NeedFlush determines whether to flush at this point. | |
| func (e *encoderState) NeedFlush() 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 e.wr != nil && (e.Tokens.Depth() == 1 || len(e.Buf) > 3*cap(e.Buf)/4) | |
| } | |
| // Flush flushes the buffer to the underlying io.Writer. | |
| // It may append a trailing newline after the top-level value. | |
| func (e *encoderState) Flush() error { | |
| if e.wr == nil || e.avoidFlush() { | |
| return nil | |
| } | |
| // In streaming mode, always emit a newline after the top-level value. | |
| if e.Tokens.Depth() == 1 && !e.Flags.Get(jsonflags.OmitTopLevelNewline) { | |
| e.Buf = append(e.Buf, '\n') | |
| } | |
| // Inform objectNameStack that we are about to flush the buffer content. | |
| e.Names.copyQuotedBuffer(e.Buf) | |
| // Specialize bytes.Buffer for better performance. | |
| if bb, ok := e.wr.(*bytes.Buffer); ok { | |
| // 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. | |
| n, _ := bb.Write(e.Buf) // never fails unless bb is nil | |
| e.baseOffset += int64(n) | |
| // 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 avail := bb.Available(); avail < bb.Len()/4 { | |
| bb.Grow(avail + 1) | |
| } | |
| e.Buf = bb.AvailableBuffer() | |
| return nil | |
| } | |
| // Flush the internal buffer to the underlying io.Writer. | |
| n, err := e.wr.Write(e.Buf) | |
| e.baseOffset += int64(n) | |
| if err != 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 n > 0 { | |
| e.Buf = e.Buf[:copy(e.Buf, e.Buf[n:])] | |
| } | |
| return &ioError{action: "write", err: err} | |
| } | |
| e.Buf = e.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 maxBufferSize = 4 << 10 | |
| const growthSizeFactor = 2 // higher value is faster | |
| const growthRateFactor = 2 // higher value is slower | |
| // By default, grow if below the maximum buffer size. | |
| grow := cap(e.Buf) <= maxBufferSize/growthSizeFactor | |
| // Growing can be expensive, so only grow | |
| // if a sufficient number of bytes have been processed. | |
| grow = grow && int64(cap(e.Buf)) < e.previousOffsetEnd()/growthRateFactor | |
| if grow { | |
| e.Buf = make([]byte, 0, cap(e.Buf)*growthSizeFactor) | |
| } | |
| return nil | |
| } | |
| func (e *encodeBuffer) offsetAt(pos int) int64 { return e.baseOffset + int64(pos) } | |
| func (e *encodeBuffer) previousOffsetEnd() int64 { return e.baseOffset + int64(len(e.Buf)) } | |
| func (e *encodeBuffer) unflushedBuffer() []byte { return e.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 (e *encoderState) avoidFlush() bool { | |
| switch { | |
| case e.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. | |
| return true | |
| case e.Tokens.Last.needObjectValue(): | |
| // Never flush before the object value since we don't know yet | |
| // if the object value will end up being empty. | |
| return true | |
| case e.Tokens.Last.NeedObjectName() && len(e.Buf) >= 2: | |
| // Never flush after the object value if it does turn out to be empty. | |
| switch string(e.Buf[len(e.Buf)-2:]) { | |
| case `ll`, `""`, `{}`, `[]`: // last two bytes of every empty value | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // UnwriteEmptyObjectMember unwrites the last object member if it is empty | |
| // and reports whether it performed an unwrite operation. | |
| func (e *encoderState) UnwriteEmptyObjectMember(prevName *string) bool { | |
| if last := e.Tokens.Last; !last.isObject() || !last.NeedObjectName() || last.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. | |
| b := e.unflushedBuffer() | |
| // Detect whether the last value was empty. | |
| var n int | |
| if len(b) >= 3 { | |
| switch string(b[len(b)-2:]) { | |
| case "ll": // last two bytes of `null` | |
| n = 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 b[len(b)-3] == '\\' { | |
| return false // e.g., `"\""` is not empty | |
| } | |
| n = len(`""`) | |
| case `{}`: | |
| n = len(`{}`) | |
| case `[]`: | |
| n = len(`[]`) | |
| } | |
| } | |
| if n == 0 { | |
| return false | |
| } | |
| // Unwrite the value, whitespace, colon, name, whitespace, and comma. | |
| b = b[:len(b)-n] | |
| b = jsonwire.TrimSuffixWhitespace(b) | |
| b = jsonwire.TrimSuffixByte(b, ':') | |
| b = jsonwire.TrimSuffixString(b) | |
| b = jsonwire.TrimSuffixWhitespace(b) | |
| b = jsonwire.TrimSuffixByte(b, ',') | |
| e.Buf = b // store back truncated unflushed buffer | |
| // Undo state changes. | |
| e.Tokens.Last.decrement() // for object member value | |
| e.Tokens.Last.decrement() // for object member name | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| if e.Tokens.Last.isActiveNamespace() { | |
| e.Namespaces.Last().removeLast() | |
| } | |
| } | |
| e.Names.clearLast() | |
| if prevName != nil { | |
| e.Names.copyQuotedBuffer(e.Buf) // required by objectNameStack.replaceLastUnquotedName | |
| e.Names.replaceLastUnquotedName(*prevName) | |
| } | |
| return true | |
| } | |
| // UnwriteOnlyObjectMemberName unwrites the only object member name | |
| // and returns the unquoted name. | |
| func (e *encoderState) UnwriteOnlyObjectMemberName() string { | |
| if last := e.Tokens.Last; !last.isObject() || last.Length() != 1 { | |
| panic("BUG: must be called on an object after writing first name") | |
| } | |
| // Unwrite the name and whitespace. | |
| b := jsonwire.TrimSuffixString(e.Buf) | |
| isVerbatim := bytes.IndexByte(e.Buf[len(b):], '\\') < 0 | |
| name := string(jsonwire.UnquoteMayCopy(e.Buf[len(b):], isVerbatim)) | |
| e.Buf = jsonwire.TrimSuffixWhitespace(b) | |
| // Undo state changes. | |
| e.Tokens.Last.decrement() | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| if e.Tokens.Last.isActiveNamespace() { | |
| e.Namespaces.Last().removeLast() | |
| } | |
| } | |
| e.Names.clearLast() | |
| return name | |
| } | |
| // 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 WriteToken reports a [SyntacticError] and | |
| // the internal state remains unchanged. The offset reported | |
| // in [SyntacticError] will be the [Encoder.OutputOffset] plus any delimiter | |
| // or whitespace characters that would have preceded the provided token. | |
| func (e *Encoder) WriteToken(t Token) error { | |
| return e.s.WriteToken(t) | |
| } | |
| func (e *encoderState) WriteToken(t Token) error { | |
| k := t.Kind() | |
| b := e.Buf // use local variable to avoid mutating e in case of error | |
| // Append any delimiters or optional whitespace. | |
| b = e.Tokens.MayAppendDelim(b, k) | |
| if e.Flags.Get(jsonflags.AnyWhitespace) { | |
| b = e.appendWhitespace(b, k) | |
| } | |
| pos := len(b) // offset before the token | |
| // Append the token to the output and to the state machine. | |
| var err error | |
| switch k { | |
| case 'n': | |
| b = append(b, "null"...) | |
| err = e.Tokens.appendLiteral() | |
| case 'f': | |
| b = append(b, "false"...) | |
| err = e.Tokens.appendLiteral() | |
| case 't': | |
| b = append(b, "true"...) | |
| err = e.Tokens.appendLiteral() | |
| case '"': | |
| if b, err = t.appendString(b, &e.Flags); err != nil { | |
| break | |
| } | |
| if e.Tokens.Last.NeedObjectName() { | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| if !e.Tokens.Last.isValidNamespace() { | |
| err = errInvalidNamespace | |
| break | |
| } | |
| if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) { | |
| err = wrapWithObjectName(ErrDuplicateName, b[pos:]) | |
| break | |
| } | |
| } | |
| e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds | |
| } | |
| err = e.Tokens.appendString() | |
| case '0': | |
| if b, err = t.appendNumber(b, &e.Flags); err != nil { | |
| break | |
| } | |
| err = e.Tokens.appendNumber() | |
| case '{': | |
| b = append(b, '{') | |
| if err = e.Tokens.pushObject(); err != nil { | |
| break | |
| } | |
| e.Names.push() | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| e.Namespaces.push() | |
| } | |
| e.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth | |
| case '}': | |
| b = append(b, '}') | |
| if err = e.Tokens.popObject(); err != nil { | |
| break | |
| } | |
| e.Names.pop() | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| e.Namespaces.pop() | |
| } | |
| case '[': | |
| b = append(b, '[') | |
| err = e.Tokens.pushArray() | |
| e.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth | |
| case ']': | |
| b = append(b, ']') | |
| err = e.Tokens.popArray() | |
| default: | |
| err = errInvalidToken | |
| } | |
| if err != nil { | |
| return wrapSyntacticError(e, err, pos, +1) | |
| } | |
| // Finish off the buffer and store it back into e. | |
| e.Buf = b | |
| if e.NeedFlush() { | |
| return e.Flush() | |
| } | |
| return nil | |
| } | |
| // 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 (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) ([]byte, error)) error { | |
| b := e.Buf // use local variable to avoid mutating e in case of error | |
| // Append any delimiters or optional whitespace. | |
| b = e.Tokens.MayAppendDelim(b, k) | |
| if e.Flags.Get(jsonflags.AnyWhitespace) { | |
| b = e.appendWhitespace(b, k) | |
| } | |
| pos := len(b) // offset before the token | |
| var err error | |
| switch k { | |
| case '"': | |
| // Append directly into the encoder buffer by assuming that | |
| // most of the time none of the characters need escaping. | |
| b = append(b, '"') | |
| if b, err = appendFn(b); err != nil { | |
| return err | |
| } | |
| b = append(b, '"') | |
| // Check whether we need to escape the string and if necessary | |
| // copy it to a scratch buffer and then escape it back. | |
| isVerbatim := safeASCII || !jsonwire.NeedEscape(b[pos+len(`"`):len(b)-len(`"`)]) | |
| if !isVerbatim { | |
| var err error | |
| b2 := append(e.availBuffer, b[pos+len(`"`):len(b)-len(`"`)]...) | |
| b, err = jsonwire.AppendQuote(b[:pos], b2, &e.Flags) | |
| e.availBuffer = b2[:0] | |
| if err != nil { | |
| return wrapSyntacticError(e, err, pos, +1) | |
| } | |
| } | |
| // Update the state machine. | |
| if e.Tokens.Last.NeedObjectName() { | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| if !e.Tokens.Last.isValidNamespace() { | |
| return wrapSyntacticError(e, errInvalidNamespace, pos, +1) | |
| } | |
| if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) { | |
| err = wrapWithObjectName(ErrDuplicateName, b[pos:]) | |
| return wrapSyntacticError(e, err, pos, +1) | |
| } | |
| } | |
| e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds | |
| } | |
| if err := e.Tokens.appendString(); err != nil { | |
| return wrapSyntacticError(e, err, pos, +1) | |
| } | |
| case '0': | |
| if b, err = appendFn(b); err != nil { | |
| return err | |
| } | |
| if err := e.Tokens.appendNumber(); err != nil { | |
| return wrapSyntacticError(e, err, pos, +1) | |
| } | |
| default: | |
| panic("BUG: invalid kind") | |
| } | |
| // Finish off the buffer and store it back into e. | |
| e.Buf = b | |
| if e.NeedFlush() { | |
| return e.Flush() | |
| } | |
| return nil | |
| } | |
| // 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 WriteValue reports a [SyntacticError] and the internal state remains unchanged. | |
| // The offset reported in [SyntacticError] will be the [Encoder.OutputOffset] | |
| // plus the offset into v of any encountered syntax error. | |
| func (e *Encoder) WriteValue(v Value) error { | |
| return e.s.WriteValue(v) | |
| } | |
| func (e *encoderState) WriteValue(v Value) error { | |
| e.maxValue |= len(v) // bitwise OR is a fast approximation of max | |
| k := v.Kind() | |
| b := e.Buf // use local variable to avoid mutating e in case of error | |
| // Append any delimiters or optional whitespace. | |
| b = e.Tokens.MayAppendDelim(b, k) | |
| if e.Flags.Get(jsonflags.AnyWhitespace) { | |
| b = e.appendWhitespace(b, k) | |
| } | |
| pos := len(b) // offset before the value | |
| // Append the value to the output. | |
| var n int | |
| n += jsonwire.ConsumeWhitespace(v[n:]) | |
| b, m, err := e.reformatValue(b, v[n:], e.Tokens.Depth()) | |
| if err != nil { | |
| return wrapSyntacticError(e, err, pos+n+m, +1) | |
| } | |
| n += m | |
| n += jsonwire.ConsumeWhitespace(v[n:]) | |
| if len(v) > n { | |
| err = jsonwire.NewInvalidCharacterError(v[n:], "after top-level value") | |
| return wrapSyntacticError(e, err, pos+n, 0) | |
| } | |
| // Append the kind to the state machine. | |
| switch k { | |
| case 'n', 'f', 't': | |
| err = e.Tokens.appendLiteral() | |
| case '"': | |
| if e.Tokens.Last.NeedObjectName() { | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| if !e.Tokens.Last.isValidNamespace() { | |
| err = errInvalidNamespace | |
| break | |
| } | |
| if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) { | |
| err = wrapWithObjectName(ErrDuplicateName, b[pos:]) | |
| break | |
| } | |
| } | |
| e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds | |
| } | |
| err = e.Tokens.appendString() | |
| case '0': | |
| err = e.Tokens.appendNumber() | |
| case '{': | |
| if err = e.Tokens.pushObject(); err != nil { | |
| break | |
| } | |
| if err = e.Tokens.popObject(); err != nil { | |
| panic("BUG: popObject should never fail immediately after pushObject: " + err.Error()) | |
| } | |
| if e.Flags.Get(jsonflags.ReorderRawObjects) { | |
| mustReorderObjects(b[pos:]) | |
| } | |
| case '[': | |
| if err = e.Tokens.pushArray(); err != nil { | |
| break | |
| } | |
| if err = e.Tokens.popArray(); err != nil { | |
| panic("BUG: popArray should never fail immediately after pushArray: " + err.Error()) | |
| } | |
| if e.Flags.Get(jsonflags.ReorderRawObjects) { | |
| mustReorderObjects(b[pos:]) | |
| } | |
| } | |
| if err != nil { | |
| return wrapSyntacticError(e, err, pos, +1) | |
| } | |
| // Finish off the buffer and store it back into e. | |
| e.Buf = b | |
| if e.NeedFlush() { | |
| return e.Flush() | |
| } | |
| return nil | |
| } | |
| // 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 (e *encoderState) CountNextDelimWhitespace() (n int) { | |
| const next = Kind('"') // arbitrary kind as next JSON value | |
| delim := e.Tokens.needDelim(next) | |
| if delim > 0 { | |
| n += len(",") | len(":") | |
| } | |
| if delim == ':' { | |
| if e.Flags.Get(jsonflags.SpaceAfterColon) { | |
| n += len(" ") | |
| } | |
| } else { | |
| if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) { | |
| n += len(" ") | |
| } | |
| if e.Flags.Get(jsonflags.Multiline) { | |
| if m := e.Tokens.NeedIndent(next); m > 0 { | |
| n += len("\n") + len(e.IndentPrefix) + (m-1)*len(e.Indent) | |
| } | |
| } | |
| } | |
| return n | |
| } | |
| // appendWhitespace appends whitespace that immediately precedes the next token. | |
| func (e *encoderState) appendWhitespace(b []byte, next Kind) []byte { | |
| if delim := e.Tokens.needDelim(next); delim == ':' { | |
| if e.Flags.Get(jsonflags.SpaceAfterColon) { | |
| b = append(b, ' ') | |
| } | |
| } else { | |
| if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) { | |
| b = append(b, ' ') | |
| } | |
| if e.Flags.Get(jsonflags.Multiline) { | |
| b = e.AppendIndent(b, e.Tokens.NeedIndent(next)) | |
| } | |
| } | |
| return b | |
| } | |
| // AppendIndent appends the appropriate number of indentation characters | |
| // for the current nested level, n. | |
| func (e *encoderState) AppendIndent(b []byte, n int) []byte { | |
| if n == 0 { | |
| return b | |
| } | |
| b = append(b, '\n') | |
| b = append(b, e.IndentPrefix...) | |
| for ; n > 1; n-- { | |
| b = append(b, e.Indent...) | |
| } | |
| return b | |
| } | |
| // 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 (e *encoderState) reformatValue(dst []byte, src Value, depth int) ([]byte, int, error) { | |
| // TODO: Should this update ValueFlags as input? | |
| if len(src) == 0 { | |
| return dst, 0, io.ErrUnexpectedEOF | |
| } | |
| switch k := Kind(src[0]).normalize(); k { | |
| case 'n': | |
| if jsonwire.ConsumeNull(src) == 0 { | |
| n, err := jsonwire.ConsumeLiteral(src, "null") | |
| return dst, n, err | |
| } | |
| return append(dst, "null"...), len("null"), nil | |
| case 'f': | |
| if jsonwire.ConsumeFalse(src) == 0 { | |
| n, err := jsonwire.ConsumeLiteral(src, "false") | |
| return dst, n, err | |
| } | |
| return append(dst, "false"...), len("false"), nil | |
| case 't': | |
| if jsonwire.ConsumeTrue(src) == 0 { | |
| n, err := jsonwire.ConsumeLiteral(src, "true") | |
| return dst, n, err | |
| } | |
| return append(dst, "true"...), len("true"), nil | |
| case '"': | |
| if n := jsonwire.ConsumeSimpleString(src); n > 0 { | |
| dst = append(dst, src[:n]...) // copy simple strings verbatim | |
| return dst, n, nil | |
| } | |
| return jsonwire.ReformatString(dst, src, &e.Flags) | |
| case '0': | |
| if n := jsonwire.ConsumeSimpleNumber(src); n > 0 && !e.Flags.Get(jsonflags.CanonicalizeNumbers) { | |
| dst = append(dst, src[:n]...) // copy simple numbers verbatim | |
| return dst, n, nil | |
| } | |
| return jsonwire.ReformatNumber(dst, src, &e.Flags) | |
| case '{': | |
| return e.reformatObject(dst, src, depth) | |
| case '[': | |
| return e.reformatArray(dst, src, depth) | |
| default: | |
| return dst, 0, jsonwire.NewInvalidCharacterError(src, "at start of value") | |
| } | |
| } | |
| // reformatObject parses a JSON object 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 (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte, int, error) { | |
| // Append object begin. | |
| if len(src) == 0 || src[0] != '{' { | |
| panic("BUG: reformatObject must be called with a buffer that starts with '{'") | |
| } else if depth == maxNestingDepth+1 { | |
| return dst, 0, errMaxDepth | |
| } | |
| dst = append(dst, '{') | |
| n := len("{") | |
| // Append (possible) object end. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, io.ErrUnexpectedEOF | |
| } | |
| if src[n] == '}' { | |
| dst = append(dst, '}') | |
| n += len("}") | |
| return dst, n, nil | |
| } | |
| var err error | |
| var names *objectNamespace | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) { | |
| e.Namespaces.push() | |
| defer e.Namespaces.pop() | |
| names = e.Namespaces.Last() | |
| } | |
| depth++ | |
| for { | |
| // Append optional newline and indentation. | |
| if e.Flags.Get(jsonflags.Multiline) { | |
| dst = e.AppendIndent(dst, depth) | |
| } | |
| // Append object name. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, io.ErrUnexpectedEOF | |
| } | |
| m := jsonwire.ConsumeSimpleString(src[n:]) | |
| isVerbatim := m > 0 | |
| if isVerbatim { | |
| dst = append(dst, src[n:n+m]...) | |
| } else { | |
| dst, m, err = jsonwire.ReformatString(dst, src[n:], &e.Flags) | |
| if err != nil { | |
| return dst, n + m, err | |
| } | |
| } | |
| quotedName := src[n : n+m] | |
| if !e.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(quotedName, isVerbatim) { | |
| return dst, n, wrapWithObjectName(ErrDuplicateName, quotedName) | |
| } | |
| n += m | |
| // Append colon. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName) | |
| } | |
| if src[n] != ':' { | |
| err = jsonwire.NewInvalidCharacterError(src[n:], "after object name (expecting ':')") | |
| return dst, n, wrapWithObjectName(err, quotedName) | |
| } | |
| dst = append(dst, ':') | |
| n += len(":") | |
| if e.Flags.Get(jsonflags.SpaceAfterColon) { | |
| dst = append(dst, ' ') | |
| } | |
| // Append object value. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName) | |
| } | |
| dst, m, err = e.reformatValue(dst, src[n:], depth) | |
| if err != nil { | |
| return dst, n + m, wrapWithObjectName(err, quotedName) | |
| } | |
| n += m | |
| // Append comma or object end. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, io.ErrUnexpectedEOF | |
| } | |
| switch src[n] { | |
| case ',': | |
| dst = append(dst, ',') | |
| if e.Flags.Get(jsonflags.SpaceAfterComma) { | |
| dst = append(dst, ' ') | |
| } | |
| n += len(",") | |
| continue | |
| case '}': | |
| if e.Flags.Get(jsonflags.Multiline) { | |
| dst = e.AppendIndent(dst, depth-1) | |
| } | |
| dst = append(dst, '}') | |
| n += len("}") | |
| return dst, n, nil | |
| default: | |
| return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "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 (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte, int, error) { | |
| // Append array begin. | |
| if len(src) == 0 || src[0] != '[' { | |
| panic("BUG: reformatArray must be called with a buffer that starts with '['") | |
| } else if depth == maxNestingDepth+1 { | |
| return dst, 0, errMaxDepth | |
| } | |
| dst = append(dst, '[') | |
| n := len("[") | |
| // Append (possible) array end. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, io.ErrUnexpectedEOF | |
| } | |
| if src[n] == ']' { | |
| dst = append(dst, ']') | |
| n += len("]") | |
| return dst, n, nil | |
| } | |
| var idx int64 | |
| var err error | |
| depth++ | |
| for { | |
| // Append optional newline and indentation. | |
| if e.Flags.Get(jsonflags.Multiline) { | |
| dst = e.AppendIndent(dst, depth) | |
| } | |
| // Append array value. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, io.ErrUnexpectedEOF | |
| } | |
| var m int | |
| dst, m, err = e.reformatValue(dst, src[n:], depth) | |
| if err != nil { | |
| return dst, n + m, wrapWithArrayIndex(err, idx) | |
| } | |
| n += m | |
| // Append comma or array end. | |
| n += jsonwire.ConsumeWhitespace(src[n:]) | |
| if uint(len(src)) <= uint(n) { | |
| return dst, n, io.ErrUnexpectedEOF | |
| } | |
| switch src[n] { | |
| case ',': | |
| dst = append(dst, ',') | |
| if e.Flags.Get(jsonflags.SpaceAfterComma) { | |
| dst = append(dst, ' ') | |
| } | |
| n += len(",") | |
| idx++ | |
| continue | |
| case ']': | |
| if e.Flags.Get(jsonflags.Multiline) { | |
| dst = e.AppendIndent(dst, depth-1) | |
| } | |
| dst = append(dst, ']') | |
| n += len("]") | |
| return dst, n, nil | |
| default: | |
| return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "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. | |
| func (e *Encoder) OutputOffset() int64 { | |
| return e.s.previousOffsetEnd() | |
| } | |
| // AvailableBuffer 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 := e.AvailableBuffer() | |
| // b = append(b, '"') | |
| // b = appendString(b, v) // append the string formatting of v | |
| // b = append(b, '"') | |
| // ... := e.WriteValue(b) | |
| // | |
| // Note that WriteValue expects a valid JSON value. Constructing a value in a | |
| // raw []byte requires more care than using constructor functions like | |
| // [String], which always return valid [Token]s or [Value]s. | |
| func (e *Encoder) AvailableBuffer() []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. | |
| n := 1 << bits.Len(uint(e.s.maxValue|63)) // fast approximation for max length | |
| if cap(e.s.availBuffer) < n { | |
| e.s.availBuffer = make([]byte, 0, n) | |
| } | |
| return e.s.availBuffer | |
| } | |
| // 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 a [BeginObject] or [BeginArray] token is encountered | |
| // and decremented whenever an [EndObject] or [EndArray] token is encountered. | |
| // | |
| // StackDepth returns 0 when not inside any object or array. | |
| // In particular, it returns 0 before any tokens have been written, | |
| // after any top-level value has been written, and between values | |
| // when encoding a stream of top-level values (e.g., NDJSON). | |
| // StackDepth returns 1 inside a top-level object or array, | |
| // 2 inside a nested object or array, and so on. | |
| // | |
| // For example, consider encoding the following JSON: | |
| // | |
| // {"a": [1, 2], "b": {"c": 3}} | |
| // | |
| // While encoding, StackDepth would report the following: | |
| // | |
| // - At the start, StackDepth reports 0. | |
| // - After encoding the outer '{', StackDepth reports 1. | |
| // - After encoding the inner '[', StackDepth reports 2. | |
| // - After encoding the inner ']', StackDepth reports 1. | |
| // - After encoding the outer '}', StackDepth reports 0. | |
| func (e *Encoder) StackDepth() int { | |
| // NOTE: Keep in sync with Decoder.StackDepth. | |
| return e.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: | |
| // | |
| // - [KindInvalid] for a level of zero, | |
| // - [KindBeginObject] for a level representing a JSON object, and | |
| // - [KindBeginArray] for a level representing a JSON array. | |
| // | |
| // It also reports the length of that JSON object or array encoded so far. | |
| // Each name and value in a JSON object is counted separately, | |
| // so the effective number of members is half the length. | |
| // A complete JSON object must have an even length. | |
| func (e *Encoder) StackIndex(i int) (Kind, int64) { | |
| // NOTE: Keep in sync with Decoder.StackIndex. | |
| switch s := e.s.Tokens.index(i); { | |
| case i > 0 && s.isObject(): | |
| return '{', s.Length() | |
| case i > 0 && s.isArray(): | |
| return '[', s.Length() | |
| default: | |
| return 0, s.Length() | |
| } | |
| } | |
| // StackPointer returns a JSON Pointer (RFC 6901) to the most recently written value. | |
| func (e *Encoder) StackPointer() Pointer { | |
| return Pointer(e.s.AppendStackPointer(nil, -1)) | |
| } | |
| func (e *encoderState) AppendStackPointer(b []byte, where int) []byte { | |
| e.Names.copyQuotedBuffer(e.Buf) | |
| return e.state.appendStackPointer(b, where) | |
| } |