mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-26 18:35:25 +00:00
582ab3de42
This is meant to deter users from doing byte for byte comparison. Change-Id: If005d2dc1eba45eaa4254171d2f247820db109e4 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/194037 Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
176 lines
4.6 KiB
Go
176 lines
4.6 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package json
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"google.golang.org/protobuf/internal/detrand"
|
|
"google.golang.org/protobuf/internal/errors"
|
|
)
|
|
|
|
// Encoder provides methods to write out JSON constructs and values. The user is
|
|
// responsible for producing valid sequences of JSON constructs and values.
|
|
type Encoder struct {
|
|
indent string
|
|
lastType Type
|
|
indents []byte
|
|
out []byte
|
|
}
|
|
|
|
// NewEncoder returns an Encoder.
|
|
//
|
|
// If indent is a non-empty string, it causes every entry for an Array or Object
|
|
// to be preceded by the indent and trailed by a newline.
|
|
func NewEncoder(indent string) (*Encoder, error) {
|
|
e := &Encoder{}
|
|
if len(indent) > 0 {
|
|
if strings.Trim(indent, " \t") != "" {
|
|
return nil, errors.New("indent may only be composed of space or tab characters")
|
|
}
|
|
e.indent = indent
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
// Bytes returns the content of the written bytes.
|
|
func (e *Encoder) Bytes() []byte {
|
|
return e.out
|
|
}
|
|
|
|
// WriteNull writes out the null value.
|
|
func (e *Encoder) WriteNull() {
|
|
e.prepareNext(Null)
|
|
e.out = append(e.out, "null"...)
|
|
}
|
|
|
|
// WriteBool writes out the given boolean value.
|
|
func (e *Encoder) WriteBool(b bool) {
|
|
e.prepareNext(Bool)
|
|
if b {
|
|
e.out = append(e.out, "true"...)
|
|
} else {
|
|
e.out = append(e.out, "false"...)
|
|
}
|
|
}
|
|
|
|
// WriteString writes out the given string in JSON string value.
|
|
func (e *Encoder) WriteString(s string) error {
|
|
e.prepareNext(String)
|
|
var err error
|
|
if e.out, err = appendString(e.out, s); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WriteFloat writes out the given float and bitSize in JSON number value.
|
|
func (e *Encoder) WriteFloat(n float64, bitSize int) {
|
|
e.prepareNext(Number)
|
|
e.out = appendFloat(e.out, n, bitSize)
|
|
}
|
|
|
|
// WriteInt writes out the given signed integer in JSON number value.
|
|
func (e *Encoder) WriteInt(n int64) {
|
|
e.prepareNext(Number)
|
|
e.out = append(e.out, strconv.FormatInt(n, 10)...)
|
|
}
|
|
|
|
// WriteUint writes out the given unsigned integer in JSON number value.
|
|
func (e *Encoder) WriteUint(n uint64) {
|
|
e.prepareNext(Number)
|
|
e.out = append(e.out, strconv.FormatUint(n, 10)...)
|
|
}
|
|
|
|
// StartObject writes out the '{' symbol.
|
|
func (e *Encoder) StartObject() {
|
|
e.prepareNext(StartObject)
|
|
e.out = append(e.out, '{')
|
|
}
|
|
|
|
// EndObject writes out the '}' symbol.
|
|
func (e *Encoder) EndObject() {
|
|
e.prepareNext(EndObject)
|
|
e.out = append(e.out, '}')
|
|
}
|
|
|
|
// WriteName writes out the given string in JSON string value and the name
|
|
// separator ':'.
|
|
func (e *Encoder) WriteName(s string) error {
|
|
e.prepareNext(Name)
|
|
// Errors returned by appendString() are non-fatal.
|
|
var err error
|
|
e.out, err = appendString(e.out, s)
|
|
e.out = append(e.out, ':')
|
|
return err
|
|
}
|
|
|
|
// StartArray writes out the '[' symbol.
|
|
func (e *Encoder) StartArray() {
|
|
e.prepareNext(StartArray)
|
|
e.out = append(e.out, '[')
|
|
}
|
|
|
|
// EndArray writes out the ']' symbol.
|
|
func (e *Encoder) EndArray() {
|
|
e.prepareNext(EndArray)
|
|
e.out = append(e.out, ']')
|
|
}
|
|
|
|
// prepareNext adds possible comma and indentation for the next value based
|
|
// on last type and indent option. It also updates lastType to next.
|
|
func (e *Encoder) prepareNext(next Type) {
|
|
defer func() {
|
|
// Set lastType to next.
|
|
e.lastType = next
|
|
}()
|
|
|
|
if len(e.indent) == 0 {
|
|
// Need to add comma on the following condition.
|
|
if e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0 &&
|
|
next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0 {
|
|
e.out = append(e.out, ',')
|
|
// For single-line output, add a random extra space after each
|
|
// comma to make output unstable.
|
|
if detrand.Bool() {
|
|
e.out = append(e.out, ' ')
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case e.lastType&(StartObject|StartArray) != 0:
|
|
// If next type is NOT closing, add indent and newline.
|
|
if next&(EndObject|EndArray) == 0 {
|
|
e.indents = append(e.indents, e.indent...)
|
|
e.out = append(e.out, '\n')
|
|
e.out = append(e.out, e.indents...)
|
|
}
|
|
|
|
case e.lastType&(Null|Bool|Number|String|EndObject|EndArray) != 0:
|
|
switch {
|
|
// If next type is either a value or name, add comma and newline.
|
|
case next&(Name|Null|Bool|Number|String|StartObject|StartArray) != 0:
|
|
e.out = append(e.out, ',', '\n')
|
|
|
|
// If next type is a closing object or array, adjust indentation.
|
|
case next&(EndObject|EndArray) != 0:
|
|
e.indents = e.indents[:len(e.indents)-len(e.indent)]
|
|
e.out = append(e.out, '\n')
|
|
}
|
|
e.out = append(e.out, e.indents...)
|
|
|
|
case e.lastType&Name != 0:
|
|
e.out = append(e.out, ' ')
|
|
// For multi-line output, add a random extra space after key: to make
|
|
// output unstable.
|
|
if detrand.Bool() {
|
|
e.out = append(e.out, ' ')
|
|
}
|
|
}
|
|
}
|