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