blob: 741f34f7a2001cec6f59b09f7dd8306adae71d04 [file] [log] [blame]
Joe Tsai879b18d2018-08-03 17:22:24 -07001// 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 (
Herbie Ongd3f8f2d2019-03-06 00:28:23 -08008 "strconv"
Joe Tsai879b18d2018-08-03 17:22:24 -07009 "strings"
10
Herbie Ong582ab3d2019-09-06 15:56:09 -070011 "google.golang.org/protobuf/internal/detrand"
Damien Neile89e6242019-05-13 23:55:40 -070012 "google.golang.org/protobuf/internal/errors"
Joe Tsai879b18d2018-08-03 17:22:24 -070013)
14
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080015// Encoder provides methods to write out JSON constructs and values. The user is
16// responsible for producing valid sequences of JSON constructs and values.
17type Encoder struct {
18 indent string
19 lastType Type
20 indents []byte
21 out []byte
22}
23
24// NewEncoder returns an Encoder.
Joe Tsai879b18d2018-08-03 17:22:24 -070025//
26// If indent is a non-empty string, it causes every entry for an Array or Object
27// to be preceded by the indent and trailed by a newline.
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080028func NewEncoder(indent string) (*Encoder, error) {
29 e := &Encoder{}
Joe Tsai879b18d2018-08-03 17:22:24 -070030 if len(indent) > 0 {
31 if strings.Trim(indent, " \t") != "" {
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080032 return nil, errors.New("indent may only be composed of space or tab characters")
Joe Tsai879b18d2018-08-03 17:22:24 -070033 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080034 e.indent = indent
Joe Tsai879b18d2018-08-03 17:22:24 -070035 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080036 return e, nil
Joe Tsai879b18d2018-08-03 17:22:24 -070037}
38
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080039// Bytes returns the content of the written bytes.
40func (e *Encoder) Bytes() []byte {
41 return e.out
Joe Tsai879b18d2018-08-03 17:22:24 -070042}
43
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080044// WriteNull writes out the null value.
45func (e *Encoder) WriteNull() {
46 e.prepareNext(Null)
47 e.out = append(e.out, "null"...)
48}
49
50// WriteBool writes out the given boolean value.
51func (e *Encoder) WriteBool(b bool) {
52 e.prepareNext(Bool)
53 if b {
54 e.out = append(e.out, "true"...)
55 } else {
56 e.out = append(e.out, "false"...)
Joe Tsai879b18d2018-08-03 17:22:24 -070057 }
58}
59
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080060// WriteString writes out the given string in JSON string value.
61func (e *Encoder) WriteString(s string) error {
62 e.prepareNext(String)
63 var err error
64 if e.out, err = appendString(e.out, s); err != nil {
65 return err
Joe Tsai879b18d2018-08-03 17:22:24 -070066 }
Joe Tsai879b18d2018-08-03 17:22:24 -070067 return nil
68}
69
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080070// WriteFloat writes out the given float and bitSize in JSON number value.
71func (e *Encoder) WriteFloat(n float64, bitSize int) {
72 e.prepareNext(Number)
73 e.out = appendFloat(e.out, n, bitSize)
74}
75
76// WriteInt writes out the given signed integer in JSON number value.
77func (e *Encoder) WriteInt(n int64) {
78 e.prepareNext(Number)
79 e.out = append(e.out, strconv.FormatInt(n, 10)...)
80}
81
82// WriteUint writes out the given unsigned integer in JSON number value.
83func (e *Encoder) WriteUint(n uint64) {
84 e.prepareNext(Number)
85 e.out = append(e.out, strconv.FormatUint(n, 10)...)
86}
87
88// StartObject writes out the '{' symbol.
89func (e *Encoder) StartObject() {
90 e.prepareNext(StartObject)
91 e.out = append(e.out, '{')
92}
93
94// EndObject writes out the '}' symbol.
95func (e *Encoder) EndObject() {
96 e.prepareNext(EndObject)
97 e.out = append(e.out, '}')
98}
99
100// WriteName writes out the given string in JSON string value and the name
101// separator ':'.
102func (e *Encoder) WriteName(s string) error {
103 e.prepareNext(Name)
104 // Errors returned by appendString() are non-fatal.
105 var err error
106 e.out, err = appendString(e.out, s)
107 e.out = append(e.out, ':')
108 return err
109}
110
111// StartArray writes out the '[' symbol.
112func (e *Encoder) StartArray() {
113 e.prepareNext(StartArray)
114 e.out = append(e.out, '[')
115}
116
117// EndArray writes out the ']' symbol.
118func (e *Encoder) EndArray() {
119 e.prepareNext(EndArray)
120 e.out = append(e.out, ']')
121}
122
123// prepareNext adds possible comma and indentation for the next value based
124// on last type and indent option. It also updates lastType to next.
125func (e *Encoder) prepareNext(next Type) {
126 defer func() {
127 // Set lastType to next.
128 e.lastType = next
129 }()
130
131 if len(e.indent) == 0 {
132 // Need to add comma on the following condition.
133 if e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0 &&
134 next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0 {
135 e.out = append(e.out, ',')
Herbie Ong582ab3d2019-09-06 15:56:09 -0700136 // For single-line output, add a random extra space after each
137 // comma to make output unstable.
138 if detrand.Bool() {
139 e.out = append(e.out, ' ')
140 }
Joe Tsai879b18d2018-08-03 17:22:24 -0700141 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -0800142 return
Joe Tsai879b18d2018-08-03 17:22:24 -0700143 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -0800144
145 switch {
146 case e.lastType&(StartObject|StartArray) != 0:
147 // If next type is NOT closing, add indent and newline.
148 if next&(EndObject|EndArray) == 0 {
149 e.indents = append(e.indents, e.indent...)
150 e.out = append(e.out, '\n')
151 e.out = append(e.out, e.indents...)
152 }
153
154 case e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0:
155 switch {
156 // If next type is either a value or name, add comma and newline.
157 case next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0:
158 e.out = append(e.out, ',', '\n')
159
160 // If next type is a closing object or array, adjust indentation.
161 case next&(EndObject|EndArray) != 0:
162 e.indents = e.indents[:len(e.indents)-len(e.indent)]
163 e.out = append(e.out, '\n')
164 }
165 e.out = append(e.out, e.indents...)
166
167 case e.lastType&Name != 0:
168 e.out = append(e.out, ' ')
Herbie Ong582ab3d2019-09-06 15:56:09 -0700169 // For multi-line output, add a random extra space after key: to make
170 // output unstable.
171 if detrand.Bool() {
172 e.out = append(e.out, ' ')
173 }
Joe Tsai879b18d2018-08-03 17:22:24 -0700174 }
Joe Tsai879b18d2018-08-03 17:22:24 -0700175}