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