277 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			277 lines
		
	
	
		
			7.1 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 (
 | |
| 	"math"
 | |
| 	"math/bits"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"google.golang.org/protobuf/internal/detrand"
 | |
| 	"google.golang.org/protobuf/internal/errors"
 | |
| )
 | |
| 
 | |
| // kind represents an encoding type.
 | |
| type kind uint8
 | |
| 
 | |
| const (
 | |
| 	_ kind = (1 << iota) / 2
 | |
| 	name
 | |
| 	scalar
 | |
| 	objectOpen
 | |
| 	objectClose
 | |
| 	arrayOpen
 | |
| 	arrayClose
 | |
| )
 | |
| 
 | |
| // 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
 | |
| 	lastKind kind
 | |
| 	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(scalar)
 | |
| 	e.out = append(e.out, "null"...)
 | |
| }
 | |
| 
 | |
| // WriteBool writes out the given boolean value.
 | |
| func (e *Encoder) WriteBool(b bool) {
 | |
| 	e.prepareNext(scalar)
 | |
| 	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. Returns error
 | |
| // if input string contains invalid UTF-8.
 | |
| func (e *Encoder) WriteString(s string) error {
 | |
| 	e.prepareNext(scalar)
 | |
| 	var err error
 | |
| 	if e.out, err = appendString(e.out, s); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Sentinel error used for indicating invalid UTF-8.
 | |
| var errInvalidUTF8 = errors.New("invalid UTF-8")
 | |
| 
 | |
| func appendString(out []byte, in string) ([]byte, error) {
 | |
| 	out = append(out, '"')
 | |
| 	i := indexNeedEscapeInString(in)
 | |
| 	in, out = in[i:], append(out, in[:i]...)
 | |
| 	for len(in) > 0 {
 | |
| 		switch r, n := utf8.DecodeRuneInString(in); {
 | |
| 		case r == utf8.RuneError && n == 1:
 | |
| 			return out, errInvalidUTF8
 | |
| 		case r < ' ' || r == '"' || r == '\\':
 | |
| 			out = append(out, '\\')
 | |
| 			switch r {
 | |
| 			case '"', '\\':
 | |
| 				out = append(out, byte(r))
 | |
| 			case '\b':
 | |
| 				out = append(out, 'b')
 | |
| 			case '\f':
 | |
| 				out = append(out, 'f')
 | |
| 			case '\n':
 | |
| 				out = append(out, 'n')
 | |
| 			case '\r':
 | |
| 				out = append(out, 'r')
 | |
| 			case '\t':
 | |
| 				out = append(out, 't')
 | |
| 			default:
 | |
| 				out = append(out, 'u')
 | |
| 				out = append(out, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
 | |
| 				out = strconv.AppendUint(out, uint64(r), 16)
 | |
| 			}
 | |
| 			in = in[n:]
 | |
| 		default:
 | |
| 			i := indexNeedEscapeInString(in[n:])
 | |
| 			in, out = in[n+i:], append(out, in[:n+i]...)
 | |
| 		}
 | |
| 	}
 | |
| 	out = append(out, '"')
 | |
| 	return out, nil
 | |
| }
 | |
| 
 | |
| // indexNeedEscapeInString returns the index of the character that needs
 | |
| // escaping. If no characters need escaping, this returns the input length.
 | |
| func indexNeedEscapeInString(s string) int {
 | |
| 	for i, r := range s {
 | |
| 		if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
 | |
| 			return i
 | |
| 		}
 | |
| 	}
 | |
| 	return len(s)
 | |
| }
 | |
| 
 | |
| // WriteFloat writes out the given float and bitSize in JSON number value.
 | |
| func (e *Encoder) WriteFloat(n float64, bitSize int) {
 | |
| 	e.prepareNext(scalar)
 | |
| 	e.out = appendFloat(e.out, n, bitSize)
 | |
| }
 | |
| 
 | |
| // appendFloat formats given float in bitSize, and appends to the given []byte.
 | |
| func appendFloat(out []byte, n float64, bitSize int) []byte {
 | |
| 	switch {
 | |
| 	case math.IsNaN(n):
 | |
| 		return append(out, `"NaN"`...)
 | |
| 	case math.IsInf(n, +1):
 | |
| 		return append(out, `"Infinity"`...)
 | |
| 	case math.IsInf(n, -1):
 | |
| 		return append(out, `"-Infinity"`...)
 | |
| 	}
 | |
| 
 | |
| 	// JSON number formatting logic based on encoding/json.
 | |
| 	// See floatEncoder.encode for reference.
 | |
| 	fmt := byte('f')
 | |
| 	if abs := math.Abs(n); abs != 0 {
 | |
| 		if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) ||
 | |
| 			bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
 | |
| 			fmt = 'e'
 | |
| 		}
 | |
| 	}
 | |
| 	out = strconv.AppendFloat(out, n, fmt, -1, bitSize)
 | |
| 	if fmt == 'e' {
 | |
| 		n := len(out)
 | |
| 		if n >= 4 && out[n-4] == 'e' && out[n-3] == '-' && out[n-2] == '0' {
 | |
| 			out[n-2] = out[n-1]
 | |
| 			out = out[:n-1]
 | |
| 		}
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| // WriteInt writes out the given signed integer in JSON number value.
 | |
| func (e *Encoder) WriteInt(n int64) {
 | |
| 	e.prepareNext(scalar)
 | |
| 	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(scalar)
 | |
| 	e.out = append(e.out, strconv.FormatUint(n, 10)...)
 | |
| }
 | |
| 
 | |
| // StartObject writes out the '{' symbol.
 | |
| func (e *Encoder) StartObject() {
 | |
| 	e.prepareNext(objectOpen)
 | |
| 	e.out = append(e.out, '{')
 | |
| }
 | |
| 
 | |
| // EndObject writes out the '}' symbol.
 | |
| func (e *Encoder) EndObject() {
 | |
| 	e.prepareNext(objectClose)
 | |
| 	e.out = append(e.out, '}')
 | |
| }
 | |
| 
 | |
| // WriteName writes out the given string in JSON string value and the name
 | |
| // separator ':'. Returns error if input string contains invalid UTF-8, which
 | |
| // should not be likely as protobuf field names should be valid.
 | |
| func (e *Encoder) WriteName(s string) error {
 | |
| 	e.prepareNext(name)
 | |
| 	var err error
 | |
| 	// Append to output regardless of 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(arrayOpen)
 | |
| 	e.out = append(e.out, '[')
 | |
| }
 | |
| 
 | |
| // EndArray writes out the ']' symbol.
 | |
| func (e *Encoder) EndArray() {
 | |
| 	e.prepareNext(arrayClose)
 | |
| 	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 lastKind to next.
 | |
| func (e *Encoder) prepareNext(next kind) {
 | |
| 	defer func() {
 | |
| 		// Set lastKind to next.
 | |
| 		e.lastKind = next
 | |
| 	}()
 | |
| 
 | |
| 	if len(e.indent) == 0 {
 | |
| 		// Need to add comma on the following condition.
 | |
| 		if e.lastKind&(scalar|objectClose|arrayClose) != 0 &&
 | |
| 			next&(name|scalar|objectOpen|arrayOpen) != 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.lastKind&(objectOpen|arrayOpen) != 0:
 | |
| 		// If next type is NOT closing, add indent and newline.
 | |
| 		if next&(objectClose|arrayClose) == 0 {
 | |
| 			e.indents = append(e.indents, e.indent...)
 | |
| 			e.out = append(e.out, '\n')
 | |
| 			e.out = append(e.out, e.indents...)
 | |
| 		}
 | |
| 
 | |
| 	case e.lastKind&(scalar|objectClose|arrayClose) != 0:
 | |
| 		switch {
 | |
| 		// If next type is either a value or name, add comma and newline.
 | |
| 		case next&(name|scalar|objectOpen|arrayOpen) != 0:
 | |
| 			e.out = append(e.out, ',', '\n')
 | |
| 
 | |
| 		// If next type is a closing object or array, adjust indentation.
 | |
| 		case next&(objectClose|arrayClose) != 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.lastKind&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, ' ')
 | |
| 		}
 | |
| 	}
 | |
| }
 |