encode.go raw

   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