encode.go (7257B)
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 5 package json 6 7 import ( 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. 19 type kind uint8 20 21 const ( 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. 33 type 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. 44 func 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. 56 func (e *Encoder) Bytes() []byte { 57 return e.out 58 } 59 60 // WriteNull writes out the null value. 61 func (e *Encoder) WriteNull() { 62 e.prepareNext(scalar) 63 e.out = append(e.out, "null"...) 64 } 65 66 // WriteBool writes out the given boolean value. 67 func (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. 78 func (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. 88 var errInvalidUTF8 = errors.New("invalid UTF-8") 89 90 func 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. 130 func 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. 140 func (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. 146 func 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. 177 func (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. 183 func (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. 189 func (e *Encoder) StartObject() { 190 e.prepareNext(objectOpen) 191 e.out = append(e.out, '{') 192 } 193 194 // EndObject writes out the '}' symbol. 195 func (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. 203 func (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. 213 func (e *Encoder) StartArray() { 214 e.prepareNext(arrayOpen) 215 e.out = append(e.out, '[') 216 } 217 218 // EndArray writes out the ']' symbol. 219 func (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. 226 func (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 }