value.go raw

   1  package json
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/base64"
   6  	"math/big"
   7  	"strconv"
   8  
   9  	"github.com/aws/smithy-go/encoding"
  10  )
  11  
  12  // Value represents a JSON Value type
  13  // JSON Value types: Object, Array, String, Number, Boolean, and Null
  14  type Value struct {
  15  	w       *bytes.Buffer
  16  	scratch *[]byte
  17  }
  18  
  19  // newValue returns a new Value encoder
  20  func newValue(w *bytes.Buffer, scratch *[]byte) Value {
  21  	return Value{w: w, scratch: scratch}
  22  }
  23  
  24  // String encodes v as a JSON string
  25  func (jv Value) String(v string) {
  26  	escapeStringBytes(jv.w, []byte(v))
  27  }
  28  
  29  // Byte encodes v as a JSON number
  30  func (jv Value) Byte(v int8) {
  31  	jv.Long(int64(v))
  32  }
  33  
  34  // Short encodes v as a JSON number
  35  func (jv Value) Short(v int16) {
  36  	jv.Long(int64(v))
  37  }
  38  
  39  // Integer encodes v as a JSON number
  40  func (jv Value) Integer(v int32) {
  41  	jv.Long(int64(v))
  42  }
  43  
  44  // Long encodes v as a JSON number
  45  func (jv Value) Long(v int64) {
  46  	*jv.scratch = strconv.AppendInt((*jv.scratch)[:0], v, 10)
  47  	jv.w.Write(*jv.scratch)
  48  }
  49  
  50  // ULong encodes v as a JSON number
  51  func (jv Value) ULong(v uint64) {
  52  	*jv.scratch = strconv.AppendUint((*jv.scratch)[:0], v, 10)
  53  	jv.w.Write(*jv.scratch)
  54  }
  55  
  56  // Float encodes v as a JSON number
  57  func (jv Value) Float(v float32) {
  58  	jv.float(float64(v), 32)
  59  }
  60  
  61  // Double encodes v as a JSON number
  62  func (jv Value) Double(v float64) {
  63  	jv.float(v, 64)
  64  }
  65  
  66  func (jv Value) float(v float64, bits int) {
  67  	*jv.scratch = encoding.EncodeFloat((*jv.scratch)[:0], v, bits)
  68  	jv.w.Write(*jv.scratch)
  69  }
  70  
  71  // Boolean encodes v as a JSON boolean
  72  func (jv Value) Boolean(v bool) {
  73  	*jv.scratch = strconv.AppendBool((*jv.scratch)[:0], v)
  74  	jv.w.Write(*jv.scratch)
  75  }
  76  
  77  // Base64EncodeBytes writes v as a base64 value in JSON string
  78  func (jv Value) Base64EncodeBytes(v []byte) {
  79  	encodeByteSlice(jv.w, (*jv.scratch)[:0], v)
  80  }
  81  
  82  // Write writes v directly to the JSON document
  83  func (jv Value) Write(v []byte) {
  84  	jv.w.Write(v)
  85  }
  86  
  87  // Array returns a new Array encoder
  88  func (jv Value) Array() *Array {
  89  	return newArray(jv.w, jv.scratch)
  90  }
  91  
  92  // Object returns a new Object encoder
  93  func (jv Value) Object() *Object {
  94  	return newObject(jv.w, jv.scratch)
  95  }
  96  
  97  // Null encodes a null JSON value
  98  func (jv Value) Null() {
  99  	jv.w.WriteString(null)
 100  }
 101  
 102  // BigInteger encodes v as JSON value
 103  func (jv Value) BigInteger(v *big.Int) {
 104  	jv.w.Write([]byte(v.Text(10)))
 105  }
 106  
 107  // BigDecimal encodes v as JSON value
 108  func (jv Value) BigDecimal(v *big.Float) {
 109  	if i, accuracy := v.Int64(); accuracy == big.Exact {
 110  		jv.Long(i)
 111  		return
 112  	}
 113  	// TODO: Should this try to match ES6 ToString similar to stdlib JSON?
 114  	jv.w.Write([]byte(v.Text('e', -1)))
 115  }
 116  
 117  // Based on encoding/json encodeByteSlice from the Go Standard Library
 118  // https://golang.org/src/encoding/json/encode.go
 119  func encodeByteSlice(w *bytes.Buffer, scratch []byte, v []byte) {
 120  	if v == nil {
 121  		w.WriteString(null)
 122  		return
 123  	}
 124  
 125  	w.WriteRune(quote)
 126  
 127  	encodedLen := base64.StdEncoding.EncodedLen(len(v))
 128  	if encodedLen <= len(scratch) {
 129  		// If the encoded bytes fit in e.scratch, avoid an extra
 130  		// allocation and use the cheaper Encoding.Encode.
 131  		dst := scratch[:encodedLen]
 132  		base64.StdEncoding.Encode(dst, v)
 133  		w.Write(dst)
 134  	} else if encodedLen <= 1024 {
 135  		// The encoded bytes are short enough to allocate for, and
 136  		// Encoding.Encode is still cheaper.
 137  		dst := make([]byte, encodedLen)
 138  		base64.StdEncoding.Encode(dst, v)
 139  		w.Write(dst)
 140  	} else {
 141  		// The encoded bytes are too long to cheaply allocate, and
 142  		// Encoding.Encode is no longer noticeably cheaper.
 143  		enc := base64.NewEncoder(base64.StdEncoding, w)
 144  		enc.Write(v)
 145  		enc.Close()
 146  	}
 147  
 148  	w.WriteRune(quote)
 149  }
 150