|
Packit |
63bb0d |
// Package jsonutil provides JSON serialization of AWS requests and responses.
|
|
Packit |
63bb0d |
package jsonutil
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
import (
|
|
Packit |
63bb0d |
"bytes"
|
|
Packit |
63bb0d |
"encoding/base64"
|
|
Packit |
63bb0d |
"encoding/json"
|
|
Packit |
63bb0d |
"fmt"
|
|
Packit |
63bb0d |
"math"
|
|
Packit |
63bb0d |
"reflect"
|
|
Packit |
63bb0d |
"sort"
|
|
Packit |
63bb0d |
"strconv"
|
|
Packit |
63bb0d |
"time"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/aws"
|
|
Packit |
63bb0d |
"github.com/aws/aws-sdk-go/private/protocol"
|
|
Packit |
63bb0d |
)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
var timeType = reflect.ValueOf(time.Time{}).Type()
|
|
Packit |
63bb0d |
var byteSliceType = reflect.ValueOf([]byte{}).Type()
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// BuildJSON builds a JSON string for a given object v.
|
|
Packit |
63bb0d |
func BuildJSON(v interface{}) ([]byte, error) {
|
|
Packit |
63bb0d |
var buf bytes.Buffer
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
err := buildAny(reflect.ValueOf(v), &buf, "")
|
|
Packit |
63bb0d |
return buf.Bytes(), err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildAny(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
|
|
Packit |
63bb0d |
origVal := value
|
|
Packit |
63bb0d |
value = reflect.Indirect(value)
|
|
Packit |
63bb0d |
if !value.IsValid() {
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
vtype := value.Type()
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
t := tag.Get("type")
|
|
Packit |
63bb0d |
if t == "" {
|
|
Packit |
63bb0d |
switch vtype.Kind() {
|
|
Packit |
63bb0d |
case reflect.Struct:
|
|
Packit |
63bb0d |
// also it can't be a time object
|
|
Packit |
63bb0d |
if value.Type() != timeType {
|
|
Packit |
63bb0d |
t = "structure"
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
case reflect.Slice:
|
|
Packit |
63bb0d |
// also it can't be a byte slice
|
|
Packit |
63bb0d |
if _, ok := value.Interface().([]byte); !ok {
|
|
Packit |
63bb0d |
t = "list"
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
case reflect.Map:
|
|
Packit |
63bb0d |
// cannot be a JSONValue map
|
|
Packit |
63bb0d |
if _, ok := value.Interface().(aws.JSONValue); !ok {
|
|
Packit |
63bb0d |
t = "map"
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
switch t {
|
|
Packit |
63bb0d |
case "structure":
|
|
Packit |
63bb0d |
if field, ok := vtype.FieldByName("_"); ok {
|
|
Packit |
63bb0d |
tag = field.Tag
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return buildStruct(value, buf, tag)
|
|
Packit |
63bb0d |
case "list":
|
|
Packit |
63bb0d |
return buildList(value, buf, tag)
|
|
Packit |
63bb0d |
case "map":
|
|
Packit |
63bb0d |
return buildMap(value, buf, tag)
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return buildScalar(origVal, buf, tag)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildStruct(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
|
|
Packit |
63bb0d |
if !value.IsValid() {
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// unwrap payloads
|
|
Packit |
63bb0d |
if payload := tag.Get("payload"); payload != "" {
|
|
Packit |
63bb0d |
field, _ := value.Type().FieldByName(payload)
|
|
Packit |
63bb0d |
tag = field.Tag
|
|
Packit |
63bb0d |
value = elemOf(value.FieldByName(payload))
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if !value.IsValid() {
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
buf.WriteByte('{')
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
t := value.Type()
|
|
Packit |
63bb0d |
first := true
|
|
Packit |
63bb0d |
for i := 0; i < t.NumField(); i++ {
|
|
Packit |
63bb0d |
member := value.Field(i)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// This allocates the most memory.
|
|
Packit |
63bb0d |
// Additionally, we cannot skip nil fields due to
|
|
Packit |
63bb0d |
// idempotency auto filling.
|
|
Packit |
63bb0d |
field := t.Field(i)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if field.PkgPath != "" {
|
|
Packit |
63bb0d |
continue // ignore unexported fields
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if field.Tag.Get("json") == "-" {
|
|
Packit |
63bb0d |
continue
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if field.Tag.Get("location") != "" {
|
|
Packit |
63bb0d |
continue // ignore non-body elements
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
if field.Tag.Get("ignore") != "" {
|
|
Packit |
63bb0d |
continue
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if protocol.CanSetIdempotencyToken(member, field) {
|
|
Packit |
63bb0d |
token := protocol.GetIdempotencyToken()
|
|
Packit |
63bb0d |
member = reflect.ValueOf(&token)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if (member.Kind() == reflect.Ptr || member.Kind() == reflect.Slice || member.Kind() == reflect.Map) && member.IsNil() {
|
|
Packit |
63bb0d |
continue // ignore unset fields
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if first {
|
|
Packit |
63bb0d |
first = false
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
buf.WriteByte(',')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// figure out what this field is called
|
|
Packit |
63bb0d |
name := field.Name
|
|
Packit |
63bb0d |
if locName := field.Tag.Get("locationName"); locName != "" {
|
|
Packit |
63bb0d |
name = locName
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
writeString(name, buf)
|
|
Packit |
63bb0d |
buf.WriteString(`:`)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
err := buildAny(member, buf, field.Tag)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
buf.WriteString("}")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildList(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
|
|
Packit |
63bb0d |
buf.WriteString("[")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for i := 0; i < value.Len(); i++ {
|
|
Packit |
63bb0d |
buildAny(value.Index(i), buf, "")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
if i < value.Len()-1 {
|
|
Packit |
63bb0d |
buf.WriteString(",")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
buf.WriteString("]")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
type sortedValues []reflect.Value
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func (sv sortedValues) Len() int { return len(sv) }
|
|
Packit |
63bb0d |
func (sv sortedValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
|
|
Packit |
63bb0d |
func (sv sortedValues) Less(i, j int) bool { return sv[i].String() < sv[j].String() }
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildMap(value reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
|
|
Packit |
63bb0d |
buf.WriteString("{")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
sv := sortedValues(value.MapKeys())
|
|
Packit |
63bb0d |
sort.Sort(sv)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
for i, k := range sv {
|
|
Packit |
63bb0d |
if i > 0 {
|
|
Packit |
63bb0d |
buf.WriteByte(',')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
writeString(k.String(), buf)
|
|
Packit |
63bb0d |
buf.WriteString(`:`)
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
buildAny(value.MapIndex(k), buf, "")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
buf.WriteString("}")
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func buildScalar(v reflect.Value, buf *bytes.Buffer, tag reflect.StructTag) error {
|
|
Packit |
63bb0d |
// prevents allocation on the heap.
|
|
Packit |
63bb0d |
scratch := [64]byte{}
|
|
Packit |
63bb0d |
switch value := reflect.Indirect(v); value.Kind() {
|
|
Packit |
63bb0d |
case reflect.String:
|
|
Packit |
63bb0d |
writeString(value.String(), buf)
|
|
Packit |
63bb0d |
case reflect.Bool:
|
|
Packit |
63bb0d |
if value.Bool() {
|
|
Packit |
63bb0d |
buf.WriteString("true")
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
buf.WriteString("false")
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
case reflect.Int64:
|
|
Packit |
63bb0d |
buf.Write(strconv.AppendInt(scratch[:0], value.Int(), 10))
|
|
Packit |
63bb0d |
case reflect.Float64:
|
|
Packit |
63bb0d |
f := value.Float()
|
|
Packit |
63bb0d |
if math.IsInf(f, 0) || math.IsNaN(f) {
|
|
Packit |
63bb0d |
return &json.UnsupportedValueError{Value: v, Str: strconv.FormatFloat(f, 'f', -1, 64)}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
buf.Write(strconv.AppendFloat(scratch[:0], f, 'f', -1, 64))
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
switch converted := value.Interface().(type) {
|
|
Packit |
63bb0d |
case time.Time:
|
|
Packit |
63bb0d |
format := tag.Get("timestampFormat")
|
|
Packit |
63bb0d |
if len(format) == 0 {
|
|
Packit |
63bb0d |
format = protocol.UnixTimeFormatName
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
ts := protocol.FormatTime(format, converted)
|
|
Packit |
63bb0d |
if format != protocol.UnixTimeFormatName {
|
|
Packit |
63bb0d |
ts = `"` + ts + `"`
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
buf.WriteString(ts)
|
|
Packit |
63bb0d |
case []byte:
|
|
Packit |
63bb0d |
if !value.IsNil() {
|
|
Packit |
63bb0d |
buf.WriteByte('"')
|
|
Packit |
63bb0d |
if len(converted) < 1024 {
|
|
Packit |
63bb0d |
// for small buffers, using Encode directly is much faster.
|
|
Packit |
63bb0d |
dst := make([]byte, base64.StdEncoding.EncodedLen(len(converted)))
|
|
Packit |
63bb0d |
base64.StdEncoding.Encode(dst, converted)
|
|
Packit |
63bb0d |
buf.Write(dst)
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
// for large buffers, avoid unnecessary extra temporary
|
|
Packit |
63bb0d |
// buffer space.
|
|
Packit |
63bb0d |
enc := base64.NewEncoder(base64.StdEncoding, buf)
|
|
Packit |
63bb0d |
enc.Write(converted)
|
|
Packit |
63bb0d |
enc.Close()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
buf.WriteByte('"')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
case aws.JSONValue:
|
|
Packit |
63bb0d |
str, err := protocol.EncodeJSONValue(converted, protocol.QuotedEscape)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit |
63bb0d |
return fmt.Errorf("unable to encode JSONValue, %v", err)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
buf.WriteString(str)
|
|
Packit |
63bb0d |
default:
|
|
Packit |
63bb0d |
return fmt.Errorf("unsupported JSON value %v (%s)", value.Interface(), value.Type())
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
var hex = "0123456789abcdef"
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
func writeString(s string, buf *bytes.Buffer) {
|
|
Packit |
63bb0d |
buf.WriteByte('"')
|
|
Packit |
63bb0d |
for i := 0; i < len(s); i++ {
|
|
Packit |
63bb0d |
if s[i] == '"' {
|
|
Packit |
63bb0d |
buf.WriteString(`\"`)
|
|
Packit |
63bb0d |
} else if s[i] == '\\' {
|
|
Packit |
63bb0d |
buf.WriteString(`\\`)
|
|
Packit |
63bb0d |
} else if s[i] == '\b' {
|
|
Packit |
63bb0d |
buf.WriteString(`\b`)
|
|
Packit |
63bb0d |
} else if s[i] == '\f' {
|
|
Packit |
63bb0d |
buf.WriteString(`\f`)
|
|
Packit |
63bb0d |
} else if s[i] == '\r' {
|
|
Packit |
63bb0d |
buf.WriteString(`\r`)
|
|
Packit |
63bb0d |
} else if s[i] == '\t' {
|
|
Packit |
63bb0d |
buf.WriteString(`\t`)
|
|
Packit |
63bb0d |
} else if s[i] == '\n' {
|
|
Packit |
63bb0d |
buf.WriteString(`\n`)
|
|
Packit |
63bb0d |
} else if s[i] < 32 {
|
|
Packit |
63bb0d |
buf.WriteString("\\u00")
|
|
Packit |
63bb0d |
buf.WriteByte(hex[s[i]>>4])
|
|
Packit |
63bb0d |
buf.WriteByte(hex[s[i]&0xF])
|
|
Packit |
63bb0d |
} else {
|
|
Packit |
63bb0d |
buf.WriteByte(s[i])
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
buf.WriteByte('"')
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// Returns the reflection element of a value, if it is a pointer.
|
|
Packit |
63bb0d |
func elemOf(value reflect.Value) reflect.Value {
|
|
Packit |
63bb0d |
for value.Kind() == reflect.Ptr {
|
|
Packit |
63bb0d |
value = value.Elem()
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
return value
|
|
Packit |
63bb0d |
}
|