blob: f73e224b971133a22d45f63f7266891c622bcd57 [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
Joe Tsai01ab2962018-09-21 17:44:00 -070011 "github.com/golang/protobuf/v2/internal/errors"
Joe Tsai879b18d2018-08-03 17:22:24 -070012)
13
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080014// Encoder provides methods to write out JSON constructs and values. The user is
15// responsible for producing valid sequences of JSON constructs and values.
16type Encoder struct {
17 indent string
18 lastType Type
19 indents []byte
20 out []byte
21}
22
23// NewEncoder returns an Encoder.
Joe Tsai879b18d2018-08-03 17:22:24 -070024//
25// If indent is a non-empty string, it causes every entry for an Array or Object
26// to be preceded by the indent and trailed by a newline.
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080027func NewEncoder(indent string) (*Encoder, error) {
28 e := &Encoder{}
Joe Tsai879b18d2018-08-03 17:22:24 -070029 if len(indent) > 0 {
30 if strings.Trim(indent, " \t") != "" {
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080031 return nil, errors.New("indent may only be composed of space or tab characters")
Joe Tsai879b18d2018-08-03 17:22:24 -070032 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080033 e.indent = indent
Joe Tsai879b18d2018-08-03 17:22:24 -070034 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080035 return e, nil
Joe Tsai879b18d2018-08-03 17:22:24 -070036}
37
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080038// Bytes returns the content of the written bytes.
39func (e *Encoder) Bytes() []byte {
40 return e.out
Joe Tsai879b18d2018-08-03 17:22:24 -070041}
42
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080043// WriteNull writes out the null value.
44func (e *Encoder) WriteNull() {
45 e.prepareNext(Null)
46 e.out = append(e.out, "null"...)
47}
48
49// WriteBool writes out the given boolean value.
50func (e *Encoder) WriteBool(b bool) {
51 e.prepareNext(Bool)
52 if b {
53 e.out = append(e.out, "true"...)
54 } else {
55 e.out = append(e.out, "false"...)
Joe Tsai879b18d2018-08-03 17:22:24 -070056 }
57}
58
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080059// WriteString writes out the given string in JSON string value.
60func (e *Encoder) WriteString(s string) error {
61 e.prepareNext(String)
62 var err error
63 if e.out, err = appendString(e.out, s); err != nil {
64 return err
Joe Tsai879b18d2018-08-03 17:22:24 -070065 }
Joe Tsai879b18d2018-08-03 17:22:24 -070066 return nil
67}
68
Herbie Ongd3f8f2d2019-03-06 00:28:23 -080069// WriteFloat writes out the given float and bitSize in JSON number value.
70func (e *Encoder) WriteFloat(n float64, bitSize int) {
71 e.prepareNext(Number)
72 e.out = appendFloat(e.out, n, bitSize)
73}
74
75// WriteInt writes out the given signed integer in JSON number value.
76func (e *Encoder) WriteInt(n int64) {
77 e.prepareNext(Number)
78 e.out = append(e.out, strconv.FormatInt(n, 10)...)
79}
80
81// WriteUint writes out the given unsigned integer in JSON number value.
82func (e *Encoder) WriteUint(n uint64) {
83 e.prepareNext(Number)
84 e.out = append(e.out, strconv.FormatUint(n, 10)...)
85}
86
87// StartObject writes out the '{' symbol.
88func (e *Encoder) StartObject() {
89 e.prepareNext(StartObject)
90 e.out = append(e.out, '{')
91}
92
93// EndObject writes out the '}' symbol.
94func (e *Encoder) EndObject() {
95 e.prepareNext(EndObject)
96 e.out = append(e.out, '}')
97}
98
99// WriteName writes out the given string in JSON string value and the name
100// separator ':'.
101func (e *Encoder) WriteName(s string) error {
102 e.prepareNext(Name)
103 // Errors returned by appendString() are non-fatal.
104 var err error
105 e.out, err = appendString(e.out, s)
106 e.out = append(e.out, ':')
107 return err
108}
109
110// StartArray writes out the '[' symbol.
111func (e *Encoder) StartArray() {
112 e.prepareNext(StartArray)
113 e.out = append(e.out, '[')
114}
115
116// EndArray writes out the ']' symbol.
117func (e *Encoder) EndArray() {
118 e.prepareNext(EndArray)
119 e.out = append(e.out, ']')
120}
121
122// prepareNext adds possible comma and indentation for the next value based
123// on last type and indent option. It also updates lastType to next.
124func (e *Encoder) prepareNext(next Type) {
125 defer func() {
126 // Set lastType to next.
127 e.lastType = next
128 }()
129
130 if len(e.indent) == 0 {
131 // Need to add comma on the following condition.
132 if e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0 &&
133 next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0 {
134 e.out = append(e.out, ',')
Joe Tsai879b18d2018-08-03 17:22:24 -0700135 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -0800136 return
Joe Tsai879b18d2018-08-03 17:22:24 -0700137 }
Herbie Ongd3f8f2d2019-03-06 00:28:23 -0800138
139 switch {
140 case e.lastType&(StartObject|StartArray) != 0:
141 // If next type is NOT closing, add indent and newline.
142 if next&(EndObject|EndArray) == 0 {
143 e.indents = append(e.indents, e.indent...)
144 e.out = append(e.out, '\n')
145 e.out = append(e.out, e.indents...)
146 }
147
148 case e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0:
149 switch {
150 // If next type is either a value or name, add comma and newline.
151 case next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0:
152 e.out = append(e.out, ',', '\n')
153
154 // If next type is a closing object or array, adjust indentation.
155 case next&(EndObject|EndArray) != 0:
156 e.indents = e.indents[:len(e.indents)-len(e.indent)]
157 e.out = append(e.out, '\n')
158 }
159 e.out = append(e.out, e.indents...)
160
161 case e.lastType&Name != 0:
162 e.out = append(e.out, ' ')
Joe Tsai879b18d2018-08-03 17:22:24 -0700163 }
Joe Tsai879b18d2018-08-03 17:22:24 -0700164}