encode.go

  1// Copyright 2018 The Go Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package json
  6
  7import (
  8	"math"
  9	"math/bits"
 10	"strconv"
 11	"strings"
 12	"unicode/utf8"
 13
 14	"google.golang.org/protobuf/internal/detrand"
 15	"google.golang.org/protobuf/internal/errors"
 16)
 17
 18// kind represents an encoding type.
 19type kind uint8
 20
 21const (
 22	_ kind = (1 << iota) / 2
 23	name
 24	scalar
 25	objectOpen
 26	objectClose
 27	arrayOpen
 28	arrayClose
 29)
 30
 31// Encoder provides methods to write out JSON constructs and values. The user is
 32// responsible for producing valid sequences of JSON constructs and values.
 33type Encoder struct {
 34	indent   string
 35	lastKind kind
 36	indents  []byte
 37	out      []byte
 38}
 39
 40// NewEncoder returns an Encoder.
 41//
 42// If indent is a non-empty string, it causes every entry for an Array or Object
 43// to be preceded by the indent and trailed by a newline.
 44func NewEncoder(buf []byte, indent string) (*Encoder, error) {
 45	e := &Encoder{
 46		out: buf,
 47	}
 48	if len(indent) > 0 {
 49		if strings.Trim(indent, " \t") != "" {
 50			return nil, errors.New("indent may only be composed of space or tab characters")
 51		}
 52		e.indent = indent
 53	}
 54	return e, nil
 55}
 56
 57// Bytes returns the content of the written bytes.
 58func (e *Encoder) Bytes() []byte {
 59	return e.out
 60}
 61
 62// WriteNull writes out the null value.
 63func (e *Encoder) WriteNull() {
 64	e.prepareNext(scalar)
 65	e.out = append(e.out, "null"...)
 66}
 67
 68// WriteBool writes out the given boolean value.
 69func (e *Encoder) WriteBool(b bool) {
 70	e.prepareNext(scalar)
 71	if b {
 72		e.out = append(e.out, "true"...)
 73	} else {
 74		e.out = append(e.out, "false"...)
 75	}
 76}
 77
 78// WriteString writes out the given string in JSON string value. Returns error
 79// if input string contains invalid UTF-8.
 80func (e *Encoder) WriteString(s string) error {
 81	e.prepareNext(scalar)
 82	var err error
 83	if e.out, err = appendString(e.out, s); err != nil {
 84		return err
 85	}
 86	return nil
 87}
 88
 89// Sentinel error used for indicating invalid UTF-8.
 90var errInvalidUTF8 = errors.New("invalid UTF-8")
 91
 92func appendString(out []byte, in string) ([]byte, error) {
 93	out = append(out, '"')
 94	i := indexNeedEscapeInString(in)
 95	in, out = in[i:], append(out, in[:i]...)
 96	for len(in) > 0 {
 97		switch r, n := utf8.DecodeRuneInString(in); {
 98		case r == utf8.RuneError && n == 1:
 99			return out, errInvalidUTF8
100		case r < ' ' || r == '"' || r == '\\':
101			out = append(out, '\\')
102			switch r {
103			case '"', '\\':
104				out = append(out, byte(r))
105			case '\b':
106				out = append(out, 'b')
107			case '\f':
108				out = append(out, 'f')
109			case '\n':
110				out = append(out, 'n')
111			case '\r':
112				out = append(out, 'r')
113			case '\t':
114				out = append(out, 't')
115			default:
116				out = append(out, 'u')
117				out = append(out, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
118				out = strconv.AppendUint(out, uint64(r), 16)
119			}
120			in = in[n:]
121		default:
122			i := indexNeedEscapeInString(in[n:])
123			in, out = in[n+i:], append(out, in[:n+i]...)
124		}
125	}
126	out = append(out, '"')
127	return out, nil
128}
129
130// indexNeedEscapeInString returns the index of the character that needs
131// escaping. If no characters need escaping, this returns the input length.
132func indexNeedEscapeInString(s string) int {
133	for i, r := range s {
134		if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
135			return i
136		}
137	}
138	return len(s)
139}
140
141// WriteFloat writes out the given float and bitSize in JSON number value.
142func (e *Encoder) WriteFloat(n float64, bitSize int) {
143	e.prepareNext(scalar)
144	e.out = appendFloat(e.out, n, bitSize)
145}
146
147// appendFloat formats given float in bitSize, and appends to the given []byte.
148func appendFloat(out []byte, n float64, bitSize int) []byte {
149	switch {
150	case math.IsNaN(n):
151		return append(out, `"NaN"`...)
152	case math.IsInf(n, +1):
153		return append(out, `"Infinity"`...)
154	case math.IsInf(n, -1):
155		return append(out, `"-Infinity"`...)
156	}
157
158	// JSON number formatting logic based on encoding/json.
159	// See floatEncoder.encode for reference.
160	fmt := byte('f')
161	if abs := math.Abs(n); abs != 0 {
162		if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) ||
163			bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
164			fmt = 'e'
165		}
166	}
167	out = strconv.AppendFloat(out, n, fmt, -1, bitSize)
168	if fmt == 'e' {
169		n := len(out)
170		if n >= 4 && out[n-4] == 'e' && out[n-3] == '-' && out[n-2] == '0' {
171			out[n-2] = out[n-1]
172			out = out[:n-1]
173		}
174	}
175	return out
176}
177
178// WriteInt writes out the given signed integer in JSON number value.
179func (e *Encoder) WriteInt(n int64) {
180	e.prepareNext(scalar)
181	e.out = strconv.AppendInt(e.out, n, 10)
182}
183
184// WriteUint writes out the given unsigned integer in JSON number value.
185func (e *Encoder) WriteUint(n uint64) {
186	e.prepareNext(scalar)
187	e.out = strconv.AppendUint(e.out, n, 10)
188}
189
190// StartObject writes out the '{' symbol.
191func (e *Encoder) StartObject() {
192	e.prepareNext(objectOpen)
193	e.out = append(e.out, '{')
194}
195
196// EndObject writes out the '}' symbol.
197func (e *Encoder) EndObject() {
198	e.prepareNext(objectClose)
199	e.out = append(e.out, '}')
200}
201
202// WriteName writes out the given string in JSON string value and the name
203// separator ':'. Returns error if input string contains invalid UTF-8, which
204// should not be likely as protobuf field names should be valid.
205func (e *Encoder) WriteName(s string) error {
206	e.prepareNext(name)
207	var err error
208	// Append to output regardless of error.
209	e.out, err = appendString(e.out, s)
210	e.out = append(e.out, ':')
211	return err
212}
213
214// StartArray writes out the '[' symbol.
215func (e *Encoder) StartArray() {
216	e.prepareNext(arrayOpen)
217	e.out = append(e.out, '[')
218}
219
220// EndArray writes out the ']' symbol.
221func (e *Encoder) EndArray() {
222	e.prepareNext(arrayClose)
223	e.out = append(e.out, ']')
224}
225
226// prepareNext adds possible comma and indentation for the next value based
227// on last type and indent option. It also updates lastKind to next.
228func (e *Encoder) prepareNext(next kind) {
229	defer func() {
230		// Set lastKind to next.
231		e.lastKind = next
232	}()
233
234	if len(e.indent) == 0 {
235		// Need to add comma on the following condition.
236		if e.lastKind&(scalar|objectClose|arrayClose) != 0 &&
237			next&(name|scalar|objectOpen|arrayOpen) != 0 {
238			e.out = append(e.out, ',')
239			// For single-line output, add a random extra space after each
240			// comma to make output unstable.
241			if detrand.Bool() {
242				e.out = append(e.out, ' ')
243			}
244		}
245		return
246	}
247
248	switch {
249	case e.lastKind&(objectOpen|arrayOpen) != 0:
250		// If next type is NOT closing, add indent and newline.
251		if next&(objectClose|arrayClose) == 0 {
252			e.indents = append(e.indents, e.indent...)
253			e.out = append(e.out, '\n')
254			e.out = append(e.out, e.indents...)
255		}
256
257	case e.lastKind&(scalar|objectClose|arrayClose) != 0:
258		switch {
259		// If next type is either a value or name, add comma and newline.
260		case next&(name|scalar|objectOpen|arrayOpen) != 0:
261			e.out = append(e.out, ',', '\n')
262
263		// If next type is a closing object or array, adjust indentation.
264		case next&(objectClose|arrayClose) != 0:
265			e.indents = e.indents[:len(e.indents)-len(e.indent)]
266			e.out = append(e.out, '\n')
267		}
268		e.out = append(e.out, e.indents...)
269
270	case e.lastKind&name != 0:
271		e.out = append(e.out, ' ')
272		// For multi-line output, add a random extra space after key: to make
273		// output unstable.
274		if detrand.Bool() {
275			e.out = append(e.out, ' ')
276		}
277	}
278}