123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- // Copyright 2018 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.
- package json
- import (
- "math"
- "math/bits"
- "strconv"
- "strings"
- "unicode/utf8"
- "google.golang.org/protobuf/internal/detrand"
- "google.golang.org/protobuf/internal/errors"
- )
- // kind represents an encoding type.
- type kind uint8
- const (
- _ kind = (1 << iota) / 2
- name
- scalar
- objectOpen
- objectClose
- arrayOpen
- arrayClose
- )
- // Encoder provides methods to write out JSON constructs and values. The user is
- // responsible for producing valid sequences of JSON constructs and values.
- type Encoder struct {
- indent string
- lastKind kind
- indents []byte
- out []byte
- }
- // NewEncoder returns an Encoder.
- //
- // If indent is a non-empty string, it causes every entry for an Array or Object
- // to be preceded by the indent and trailed by a newline.
- func NewEncoder(buf []byte, indent string) (*Encoder, error) {
- e := &Encoder{
- out: buf,
- }
- if len(indent) > 0 {
- if strings.Trim(indent, " \t") != "" {
- return nil, errors.New("indent may only be composed of space or tab characters")
- }
- e.indent = indent
- }
- return e, nil
- }
- // Bytes returns the content of the written bytes.
- func (e *Encoder) Bytes() []byte {
- return e.out
- }
- // WriteNull writes out the null value.
- func (e *Encoder) WriteNull() {
- e.prepareNext(scalar)
- e.out = append(e.out, "null"...)
- }
- // WriteBool writes out the given boolean value.
- func (e *Encoder) WriteBool(b bool) {
- e.prepareNext(scalar)
- if b {
- e.out = append(e.out, "true"...)
- } else {
- e.out = append(e.out, "false"...)
- }
- }
- // WriteString writes out the given string in JSON string value. Returns error
- // if input string contains invalid UTF-8.
- func (e *Encoder) WriteString(s string) error {
- e.prepareNext(scalar)
- var err error
- if e.out, err = appendString(e.out, s); err != nil {
- return err
- }
- return nil
- }
- // Sentinel error used for indicating invalid UTF-8.
- var errInvalidUTF8 = errors.New("invalid UTF-8")
- func appendString(out []byte, in string) ([]byte, error) {
- out = append(out, '"')
- i := indexNeedEscapeInString(in)
- in, out = in[i:], append(out, in[:i]...)
- for len(in) > 0 {
- switch r, n := utf8.DecodeRuneInString(in); {
- case r == utf8.RuneError && n == 1:
- return out, errInvalidUTF8
- case r < ' ' || r == '"' || r == '\\':
- out = append(out, '\\')
- switch r {
- case '"', '\\':
- out = append(out, byte(r))
- case '\b':
- out = append(out, 'b')
- case '\f':
- out = append(out, 'f')
- case '\n':
- out = append(out, 'n')
- case '\r':
- out = append(out, 'r')
- case '\t':
- out = append(out, 't')
- default:
- out = append(out, 'u')
- out = append(out, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
- out = strconv.AppendUint(out, uint64(r), 16)
- }
- in = in[n:]
- default:
- i := indexNeedEscapeInString(in[n:])
- in, out = in[n+i:], append(out, in[:n+i]...)
- }
- }
- out = append(out, '"')
- return out, nil
- }
- // indexNeedEscapeInString returns the index of the character that needs
- // escaping. If no characters need escaping, this returns the input length.
- func indexNeedEscapeInString(s string) int {
- for i, r := range s {
- if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
- return i
- }
- }
- return len(s)
- }
- // WriteFloat writes out the given float and bitSize in JSON number value.
- func (e *Encoder) WriteFloat(n float64, bitSize int) {
- e.prepareNext(scalar)
- e.out = appendFloat(e.out, n, bitSize)
- }
- // appendFloat formats given float in bitSize, and appends to the given []byte.
- func appendFloat(out []byte, n float64, bitSize int) []byte {
- switch {
- case math.IsNaN(n):
- return append(out, `"NaN"`...)
- case math.IsInf(n, +1):
- return append(out, `"Infinity"`...)
- case math.IsInf(n, -1):
- return append(out, `"-Infinity"`...)
- }
- // JSON number formatting logic based on encoding/json.
- // See floatEncoder.encode for reference.
- fmt := byte('f')
- if abs := math.Abs(n); abs != 0 {
- if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) ||
- bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
- fmt = 'e'
- }
- }
- out = strconv.AppendFloat(out, n, fmt, -1, bitSize)
- if fmt == 'e' {
- n := len(out)
- if n >= 4 && out[n-4] == 'e' && out[n-3] == '-' && out[n-2] == '0' {
- out[n-2] = out[n-1]
- out = out[:n-1]
- }
- }
- return out
- }
- // WriteInt writes out the given signed integer in JSON number value.
- func (e *Encoder) WriteInt(n int64) {
- e.prepareNext(scalar)
- e.out = strconv.AppendInt(e.out, n, 10)
- }
- // WriteUint writes out the given unsigned integer in JSON number value.
- func (e *Encoder) WriteUint(n uint64) {
- e.prepareNext(scalar)
- e.out = strconv.AppendUint(e.out, n, 10)
- }
- // StartObject writes out the '{' symbol.
- func (e *Encoder) StartObject() {
- e.prepareNext(objectOpen)
- e.out = append(e.out, '{')
- }
- // EndObject writes out the '}' symbol.
- func (e *Encoder) EndObject() {
- e.prepareNext(objectClose)
- e.out = append(e.out, '}')
- }
- // WriteName writes out the given string in JSON string value and the name
- // separator ':'. Returns error if input string contains invalid UTF-8, which
- // should not be likely as protobuf field names should be valid.
- func (e *Encoder) WriteName(s string) error {
- e.prepareNext(name)
- var err error
- // Append to output regardless of error.
- e.out, err = appendString(e.out, s)
- e.out = append(e.out, ':')
- return err
- }
- // StartArray writes out the '[' symbol.
- func (e *Encoder) StartArray() {
- e.prepareNext(arrayOpen)
- e.out = append(e.out, '[')
- }
- // EndArray writes out the ']' symbol.
- func (e *Encoder) EndArray() {
- e.prepareNext(arrayClose)
- e.out = append(e.out, ']')
- }
- // prepareNext adds possible comma and indentation for the next value based
- // on last type and indent option. It also updates lastKind to next.
- func (e *Encoder) prepareNext(next kind) {
- defer func() {
- // Set lastKind to next.
- e.lastKind = next
- }()
- if len(e.indent) == 0 {
- // Need to add comma on the following condition.
- if e.lastKind&(scalar|objectClose|arrayClose) != 0 &&
- next&(name|scalar|objectOpen|arrayOpen) != 0 {
- e.out = append(e.out, ',')
- // For single-line output, add a random extra space after each
- // comma to make output unstable.
- if detrand.Bool() {
- e.out = append(e.out, ' ')
- }
- }
- return
- }
- switch {
- case e.lastKind&(objectOpen|arrayOpen) != 0:
- // If next type is NOT closing, add indent and newline.
- if next&(objectClose|arrayClose) == 0 {
- e.indents = append(e.indents, e.indent...)
- e.out = append(e.out, '\n')
- e.out = append(e.out, e.indents...)
- }
- case e.lastKind&(scalar|objectClose|arrayClose) != 0:
- switch {
- // If next type is either a value or name, add comma and newline.
- case next&(name|scalar|objectOpen|arrayOpen) != 0:
- e.out = append(e.out, ',', '\n')
- // If next type is a closing object or array, adjust indentation.
- case next&(objectClose|arrayClose) != 0:
- e.indents = e.indents[:len(e.indents)-len(e.indent)]
- e.out = append(e.out, '\n')
- }
- e.out = append(e.out, e.indents...)
- case e.lastKind&name != 0:
- e.out = append(e.out, ' ')
- // For multi-line output, add a random extra space after key: to make
- // output unstable.
- if detrand.Bool() {
- e.out = append(e.out, ' ')
- }
- }
- }
|