1// Package jsonutil provides JSON serialization of AWS requests and responses.
2package jsonutil
3
4import (
5	"bytes"
6	"encoding/base64"
7	"encoding/json"
8	"fmt"
9	"math"
10	"reflect"
11	"sort"
12	"strconv"
13	"time"
14
15	"github.com/aws/aws-sdk-go/aws"
16	"github.com/aws/aws-sdk-go/private/protocol"
17)
18
19var timeType = reflect.ValueOf(time.Time{}).Type()
20var byteSliceType = reflect.ValueOf([]byte{}).Type()
21
22// BuildJSON builds a JSON string for a given object v.
23func BuildJSON(v interface{}) ([]byte, error) {
24	var buf bytes.Buffer
25
26	err := buildAny(reflect.ValueOf(v), &buf, "")
27	return buf.Bytes(), err
28}
29
30func buildAny(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
31	origVal := value
32	value = reflect.Indirect(value)
33	if !value.IsValid() {
34		return nil
35	}
36
37	vtype := value.Type()
38
39	t := tag.Get("type")
40	if t == "" {
41		switch vtype.Kind() {
42		case reflect.Struct:
43			// also it can't be a time object
44			if value.Type() != timeType {
45				t = "structure"
46			}
47		case reflect.Slice:
48			// also it can't be a byte slice
49			if _, ok := value.Interface().([]byte); !ok {
50				t = "list"
51			}
52		case reflect.Map:
53			// cannot be a JSONValue map
54			if _, ok := value.Interface().(aws.JSONValue); !ok {
55				t = "map"
56			}
57		}
58	}
59
60	switch t {
61	case "structure":
62		if field, ok := vtype.FieldByName("_"); ok {
63			tag = field.Tag
64		}
65		return buildStruct(value, buf, tag)
66	case "list":
67		return buildList(value, buf, tag)
68	case "map":
69		return buildMap(value, buf, tag)
70	default:
71		return buildScalar(origVal, buf, tag)
72	}
73}
74
75func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
76	if !value.IsValid() {
77		return nil
78	}
79
80	// unwrap payloads
81	if payload := tag.Get("payload"); payload != "" {
82		field, _ := value.Type().FieldByName(payload)
83		tag = field.Tag
84		value = elemOf(value.FieldByName(payload))
85
86		if !value.IsValid() {
87			return nil
88		}
89	}
90
91	buf.WriteByte('{')
92
93	t := value.Type()
94	first := true
95	for i := 0; i < t.NumField(); i++ {
96		member := value.Field(i)
97
98		// This allocates the most memory.
99		// Additionally, we cannot skip nil fields due to
100		// idempotency auto filling.
101		field := t.Field(i)
102
103		if field.PkgPath != "" {
104			continue // ignore unexported fields
105		}
106		if field.Tag.Get("json") == "-" {
107			continue
108		}
109		if field.Tag.Get("location") != "" {
110			continue // ignore non-body elements
111		}
112		if field.Tag.Get("ignore") != "" {
113			continue
114		}
115
116		if protocol.CanSetIdempotencyToken(member, field) {
117			token := protocol.GetIdempotencyToken()
118			member = reflect.ValueOf(&token)
119		}
120
121		if (member.Kind() == reflect.Ptr || member.Kind() == reflect.Slice || member.Kind() == reflect.Map) && member.IsNil() {
122			continue // ignore unset fields
123		}
124
125		if first {
126			first = false
127		} else {
128			buf.WriteByte(',')
129		}
130
131		// figure out what this field is called
132		name := field.Name
133		if locName := field.Tag.Get("locationName"); locName != "" {
134			name = locName
135		}
136
137		writeString(name, buf)
138		buf.WriteString(`:`)
139
140		err := buildAny(member, buf, field.Tag)
141		if err != nil {
142			return err
143		}
144
145	}
146
147	buf.WriteString("}")
148
149	return nil
150}
151
152func buildList(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
153	buf.WriteString("[")
154
155	for i := 0; i < value.Len(); i++ {
156		buildAny(value.Index(i), buf, "")
157
158		if i < value.Len()-1 {
159			buf.WriteString(",")
160		}
161	}
162
163	buf.WriteString("]")
164
165	return nil
166}
167
168type sortedValues []reflect.Value
169
170func (sv sortedValues) Len() int           { return len(sv) }
171func (sv sortedValues) Swap(i, j int)      { sv[i], sv[j] = sv[j], sv[i] }
172func (sv sortedValues) Less(i, j int) bool { return sv[i].String() < sv[j].String() }
173
174func buildMap(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
175	buf.WriteString("{")
176
177	sv := sortedValues(value.MapKeys())
178	sort.Sort(sv)
179
180	for i, k := range sv {
181		if i > 0 {
182			buf.WriteByte(',')
183		}
184
185		writeString(k.String(), buf)
186		buf.WriteString(`:`)
187
188		buildAny(value.MapIndex(k), buf, "")
189	}
190
191	buf.WriteString("}")
192
193	return nil
194}
195
196func buildScalar(v reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
197	// prevents allocation on the heap.
198	scratch := [64]byte{}
199	switch value := reflect.Indirect(v); value.Kind() {
200	case reflect.String:
201		writeString(value.String(), buf)
202	case reflect.Bool:
203		if value.Bool() {
204			buf.WriteString("true")
205		} else {
206			buf.WriteString("false")
207		}
208	case reflect.Int64:
209		buf.Write(strconv.AppendInt(scratch[:0], value.Int(), 10))
210	case reflect.Float64:
211		f := value.Float()
212		if math.IsInf(f, 0) || math.IsNaN(f) {
213			return &json.UnsupportedValueError{Value: v, Str: strconv.FormatFloat(f, 'f', -1, 64)}
214		}
215		buf.Write(strconv.AppendFloat(scratch[:0], f, 'f', -1, 64))
216	default:
217		switch converted := value.Interface().(type) {
218		case time.Time:
219			format := tag.Get("timestampFormat")
220			if len(format) == 0 {
221				format = protocol.UnixTimeFormatName
222			}
223
224			ts := protocol.FormatTime(format, converted)
225			if format != protocol.UnixTimeFormatName {
226				ts = `"` + ts + `"`
227			}
228
229			buf.WriteString(ts)
230		case []byte:
231			if !value.IsNil() {
232				buf.WriteByte('"')
233				if len(converted) < 1024 {
234					// for small buffers, using Encode directly is much faster.
235					dst := make([]byte, base64.StdEncoding.EncodedLen(len(converted)))
236					base64.StdEncoding.Encode(dst, converted)
237					buf.Write(dst)
238				} else {
239					// for large buffers, avoid unnecessary extra temporary
240					// buffer space.
241					enc := base64.NewEncoder(base64.StdEncoding, buf)
242					enc.Write(converted)
243					enc.Close()
244				}
245				buf.WriteByte('"')
246			}
247		case aws.JSONValue:
248			str, err := protocol.EncodeJSONValue(converted, protocol.QuotedEscape)
249			if err != nil {
250				return fmt.Errorf("unable to encode JSONValue, %v", err)
251			}
252			buf.WriteString(str)
253		default:
254			return fmt.Errorf("unsupported JSON value %v (%s)", value.Interface(), value.Type())
255		}
256	}
257	return nil
258}
259
260var hex = "0123456789abcdef"
261
262func writeString(s string, buf *bytes.Buffer) {
263	buf.WriteByte('"')
264	for i := 0; i < len(s); i++ {
265		if s[i] == '"' {
266			buf.WriteString(`\"`)
267		} else if s[i] == '\\' {
268			buf.WriteString(`\\`)
269		} else if s[i] == '\b' {
270			buf.WriteString(`\b`)
271		} else if s[i] == '\f' {
272			buf.WriteString(`\f`)
273		} else if s[i] == '\r' {
274			buf.WriteString(`\r`)
275		} else if s[i] == '\t' {
276			buf.WriteString(`\t`)
277		} else if s[i] == '\n' {
278			buf.WriteString(`\n`)
279		} else if s[i] < 32 {
280			buf.WriteString("\\u00")
281			buf.WriteByte(hex[s[i]>>4])
282			buf.WriteByte(hex[s[i]&0xF])
283		} else {
284			buf.WriteByte(s[i])
285		}
286	}
287	buf.WriteByte('"')
288}
289
290// Returns the reflection element of a value, if it is a pointer.
291func elemOf(value reflect.Value) reflect.Value {
292	for value.Kind() == reflect.Ptr {
293		value = value.Elem()
294	}
295	return value
296}
297