...

Source file src/google.golang.org/protobuf/internal/encoding/json/encode.go

Documentation: google.golang.org/protobuf/internal/encoding/json

     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(buf []byte, indent string) (*Encoder, error) {
    45  	e := &Encoder{
    46  		out: buf,
    47  	}
    48  	if len(indent) > 0 {
    49  		if strings.Trim(indent, " \t") != "" {
    50  			return nil, errors.New("indent may only be composed of space or tab characters")
    51  		}
    52  		e.indent = indent
    53  	}
    54  	return e, nil
    55  }
    56  
    57  // Bytes returns the content of the written bytes.
    58  func (e *Encoder) Bytes() []byte {
    59  	return e.out
    60  }
    61  
    62  // WriteNull writes out the null value.
    63  func (e *Encoder) WriteNull() {
    64  	e.prepareNext(scalar)
    65  	e.out = append(e.out, "null"...)
    66  }
    67  
    68  // WriteBool writes out the given boolean value.
    69  func (e *Encoder) WriteBool(b bool) {
    70  	e.prepareNext(scalar)
    71  	if b {
    72  		e.out = append(e.out, "true"...)
    73  	} else {
    74  		e.out = append(e.out, "false"...)
    75  	}
    76  }
    77  
    78  // WriteString writes out the given string in JSON string value. Returns error
    79  // if input string contains invalid UTF-8.
    80  func (e *Encoder) WriteString(s string) error {
    81  	e.prepareNext(scalar)
    82  	var err error
    83  	if e.out, err = appendString(e.out, s); err != nil {
    84  		return err
    85  	}
    86  	return nil
    87  }
    88  
    89  // Sentinel error used for indicating invalid UTF-8.
    90  var errInvalidUTF8 = errors.New("invalid UTF-8")
    91  
    92  func appendString(out []byte, in string) ([]byte, error) {
    93  	out = append(out, '"')
    94  	i := indexNeedEscapeInString(in)
    95  	in, out = in[i:], append(out, in[:i]...)
    96  	for len(in) > 0 {
    97  		switch r, n := utf8.DecodeRuneInString(in); {
    98  		case r == utf8.RuneError && n == 1:
    99  			return out, errInvalidUTF8
   100  		case r < ' ' || r == '"' || r == '\\':
   101  			out = append(out, '\\')
   102  			switch r {
   103  			case '"', '\\':
   104  				out = append(out, byte(r))
   105  			case '\b':
   106  				out = append(out, 'b')
   107  			case '\f':
   108  				out = append(out, 'f')
   109  			case '\n':
   110  				out = append(out, 'n')
   111  			case '\r':
   112  				out = append(out, 'r')
   113  			case '\t':
   114  				out = append(out, 't')
   115  			default:
   116  				out = append(out, 'u')
   117  				out = append(out, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
   118  				out = strconv.AppendUint(out, uint64(r), 16)
   119  			}
   120  			in = in[n:]
   121  		default:
   122  			i := indexNeedEscapeInString(in[n:])
   123  			in, out = in[n+i:], append(out, in[:n+i]...)
   124  		}
   125  	}
   126  	out = append(out, '"')
   127  	return out, nil
   128  }
   129  
   130  // indexNeedEscapeInString returns the index of the character that needs
   131  // escaping. If no characters need escaping, this returns the input length.
   132  func indexNeedEscapeInString(s string) int {
   133  	for i, r := range s {
   134  		if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
   135  			return i
   136  		}
   137  	}
   138  	return len(s)
   139  }
   140  
   141  // WriteFloat writes out the given float and bitSize in JSON number value.
   142  func (e *Encoder) WriteFloat(n float64, bitSize int) {
   143  	e.prepareNext(scalar)
   144  	e.out = appendFloat(e.out, n, bitSize)
   145  }
   146  
   147  // appendFloat formats given float in bitSize, and appends to the given []byte.
   148  func appendFloat(out []byte, n float64, bitSize int) []byte {
   149  	switch {
   150  	case math.IsNaN(n):
   151  		return append(out, `"NaN"`...)
   152  	case math.IsInf(n, +1):
   153  		return append(out, `"Infinity"`...)
   154  	case math.IsInf(n, -1):
   155  		return append(out, `"-Infinity"`...)
   156  	}
   157  
   158  	// JSON number formatting logic based on encoding/json.
   159  	// See floatEncoder.encode for reference.
   160  	fmt := byte('f')
   161  	if abs := math.Abs(n); abs != 0 {
   162  		if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) ||
   163  			bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
   164  			fmt = 'e'
   165  		}
   166  	}
   167  	out = strconv.AppendFloat(out, n, fmt, -1, bitSize)
   168  	if fmt == 'e' {
   169  		n := len(out)
   170  		if n >= 4 && out[n-4] == 'e' && out[n-3] == '-' && out[n-2] == '0' {
   171  			out[n-2] = out[n-1]
   172  			out = out[:n-1]
   173  		}
   174  	}
   175  	return out
   176  }
   177  
   178  // WriteInt writes out the given signed integer in JSON number value.
   179  func (e *Encoder) WriteInt(n int64) {
   180  	e.prepareNext(scalar)
   181  	e.out = strconv.AppendInt(e.out, n, 10)
   182  }
   183  
   184  // WriteUint writes out the given unsigned integer in JSON number value.
   185  func (e *Encoder) WriteUint(n uint64) {
   186  	e.prepareNext(scalar)
   187  	e.out = strconv.AppendUint(e.out, n, 10)
   188  }
   189  
   190  // StartObject writes out the '{' symbol.
   191  func (e *Encoder) StartObject() {
   192  	e.prepareNext(objectOpen)
   193  	e.out = append(e.out, '{')
   194  }
   195  
   196  // EndObject writes out the '}' symbol.
   197  func (e *Encoder) EndObject() {
   198  	e.prepareNext(objectClose)
   199  	e.out = append(e.out, '}')
   200  }
   201  
   202  // WriteName writes out the given string in JSON string value and the name
   203  // separator ':'. Returns error if input string contains invalid UTF-8, which
   204  // should not be likely as protobuf field names should be valid.
   205  func (e *Encoder) WriteName(s string) error {
   206  	e.prepareNext(name)
   207  	var err error
   208  	// Append to output regardless of error.
   209  	e.out, err = appendString(e.out, s)
   210  	e.out = append(e.out, ':')
   211  	return err
   212  }
   213  
   214  // StartArray writes out the '[' symbol.
   215  func (e *Encoder) StartArray() {
   216  	e.prepareNext(arrayOpen)
   217  	e.out = append(e.out, '[')
   218  }
   219  
   220  // EndArray writes out the ']' symbol.
   221  func (e *Encoder) EndArray() {
   222  	e.prepareNext(arrayClose)
   223  	e.out = append(e.out, ']')
   224  }
   225  
   226  // prepareNext adds possible comma and indentation for the next value based
   227  // on last type and indent option. It also updates lastKind to next.
   228  func (e *Encoder) prepareNext(next kind) {
   229  	defer func() {
   230  		// Set lastKind to next.
   231  		e.lastKind = next
   232  	}()
   233  
   234  	if len(e.indent) == 0 {
   235  		// Need to add comma on the following condition.
   236  		if e.lastKind&(scalar|objectClose|arrayClose) != 0 &&
   237  			next&(name|scalar|objectOpen|arrayOpen) != 0 {
   238  			e.out = append(e.out, ',')
   239  			// For single-line output, add a random extra space after each
   240  			// comma to make output unstable.
   241  			if detrand.Bool() {
   242  				e.out = append(e.out, ' ')
   243  			}
   244  		}
   245  		return
   246  	}
   247  
   248  	switch {
   249  	case e.lastKind&(objectOpen|arrayOpen) != 0:
   250  		// If next type is NOT closing, add indent and newline.
   251  		if next&(objectClose|arrayClose) == 0 {
   252  			e.indents = append(e.indents, e.indent...)
   253  			e.out = append(e.out, '\n')
   254  			e.out = append(e.out, e.indents...)
   255  		}
   256  
   257  	case e.lastKind&(scalar|objectClose|arrayClose) != 0:
   258  		switch {
   259  		// If next type is either a value or name, add comma and newline.
   260  		case next&(name|scalar|objectOpen|arrayOpen) != 0:
   261  			e.out = append(e.out, ',', '\n')
   262  
   263  		// If next type is a closing object or array, adjust indentation.
   264  		case next&(objectClose|arrayClose) != 0:
   265  			e.indents = e.indents[:len(e.indents)-len(e.indent)]
   266  			e.out = append(e.out, '\n')
   267  		}
   268  		e.out = append(e.out, e.indents...)
   269  
   270  	case e.lastKind&name != 0:
   271  		e.out = append(e.out, ' ')
   272  		// For multi-line output, add a random extra space after key: to make
   273  		// output unstable.
   274  		if detrand.Bool() {
   275  			e.out = append(e.out, ' ')
   276  		}
   277  	}
   278  }
   279  

View as plain text