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