Skip to content

Commit c3a274d

Browse files
committed
aws/protocol/json: V2 JSON Encoder implementation (#457)
Adds new implementation of JSON encoder for generated marshaler to use in effort to reduce refection in SDK.
1 parent b0cd054 commit c3a274d

File tree

11 files changed

+916
-0
lines changed

11 files changed

+916
-0
lines changed

aws/protocol/json/array.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package json
2+
3+
import (
4+
"bytes"
5+
)
6+
7+
// Array represent the encoding of a JSON Array
8+
type Array struct {
9+
w *bytes.Buffer
10+
writeComma bool
11+
scratch *[]byte
12+
}
13+
14+
func newArray(w *bytes.Buffer, scratch *[]byte) *Array {
15+
w.WriteRune(leftBracket)
16+
return &Array{w: w, scratch: scratch}
17+
}
18+
19+
// Value adds a new element to the JSON Array.
20+
// Returns a Value type that is used to encode
21+
// the array element.
22+
func (a *Array) Value() Value {
23+
if a.writeComma {
24+
a.w.WriteRune(comma)
25+
} else {
26+
a.writeComma = true
27+
}
28+
29+
return newValue(a.w, a.scratch)
30+
}
31+
32+
// Close encodes the end of the JSON Array
33+
func (a *Array) Close() {
34+
a.w.WriteRune(rightBracket)
35+
}

aws/protocol/json/array_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package json
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestArray(t *testing.T) {
9+
buffer := bytes.NewBuffer(nil)
10+
scratch := make([]byte, 64)
11+
12+
array := newArray(buffer, &scratch)
13+
array.Value().String("bar")
14+
array.Value().String("baz")
15+
array.Close()
16+
17+
e := []byte(`["bar","baz"]`)
18+
if a := buffer.Bytes(); bytes.Compare(e, a) != 0 {
19+
t.Errorf("expected %+q, but got %+q", e, a)
20+
}
21+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package json_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/aws/aws-sdk-go-v2/aws"
8+
"github.com/aws/aws-sdk-go-v2/aws/protocol/json"
9+
"github.com/aws/aws-sdk-go-v2/private/protocol"
10+
v1Encoder "github.com/aws/aws-sdk-go-v2/private/protocol/json"
11+
reflectEncoder "github.com/aws/aws-sdk-go-v2/private/protocol/json/jsonutil"
12+
)
13+
14+
type testEnum string
15+
16+
type testOperationInput struct {
17+
_ struct{} `type:"structure"`
18+
StringValue *string `locationName:"stringValue" type:"string"`
19+
IntegerValue *int64 `locationName:"integerValue" type:"integer"`
20+
EnumValue testEnum `locationName:"enumValue" type:"string" enum:"true"`
21+
FloatValue *float64 `locationName:"floatValue" type:"double"`
22+
ListValue []nestedShape `locationName:"listValue" type:"list"`
23+
ShapeValue *nestedShape `locationName:"shapeValue" type:"structure"`
24+
MapValue map[string]string `locationName:"mapValue" type:"map"`
25+
ByteSlice []byte `locationName:"byteSlice" type:"blob"`
26+
}
27+
28+
func (v *testOperationInput) MarshalFields(encoder protocol.FieldEncoder) error {
29+
meta := protocol.Metadata{}
30+
31+
if v.StringValue != nil {
32+
encoder.SetValue(protocol.BodyTarget, "stringValue", protocol.QuotedValue{ValueMarshaler: protocol.StringValue(*v.StringValue)}, meta)
33+
}
34+
35+
if v.IntegerValue != nil {
36+
encoder.SetValue(protocol.BodyTarget, "integerValue", protocol.Int64Value(*v.IntegerValue), meta)
37+
}
38+
39+
if len(v.EnumValue) > 0 {
40+
encoder.SetValue(protocol.BodyTarget, "enumValue", protocol.QuotedValue{ValueMarshaler: protocol.StringValue(v.EnumValue)}, meta)
41+
}
42+
43+
if v.FloatValue != nil {
44+
encoder.SetValue(protocol.BodyTarget, "floatValue", protocol.Float64Value(*v.FloatValue), meta)
45+
}
46+
47+
if v.ListValue != nil {
48+
listEncoder := encoder.List(protocol.BodyTarget, "listValue", meta)
49+
listEncoder.Start()
50+
for i := range v.ListValue {
51+
listEncoder.ListAddFields(&v.ListValue[i])
52+
}
53+
listEncoder.End()
54+
}
55+
56+
if v.ShapeValue != nil {
57+
encoder.SetFields(protocol.BodyTarget, "shapeValue", v.ShapeValue, meta)
58+
}
59+
60+
if v.MapValue != nil {
61+
mapEncoder := encoder.Map(protocol.BodyTarget, "mapValue", meta)
62+
mapEncoder.Start()
63+
for k := range v.MapValue {
64+
mapEncoder.MapSetValue(k, protocol.QuotedValue{ValueMarshaler: protocol.StringValue(v.MapValue[k])})
65+
}
66+
mapEncoder.End()
67+
}
68+
69+
if v.ByteSlice != nil {
70+
encoder.SetValue(protocol.BodyTarget, "byteSlice", protocol.QuotedValue{ValueMarshaler: protocol.BytesValue(v.ByteSlice)}, meta)
71+
}
72+
73+
return nil
74+
}
75+
76+
type nestedShape struct {
77+
_ struct{} `type:"structure"`
78+
StringValue *string `locationName:"stringValue" type:"string"`
79+
}
80+
81+
func (v *nestedShape) MarshalFields(encoder protocol.FieldEncoder) error {
82+
meta := protocol.Metadata{}
83+
84+
if v.StringValue != nil {
85+
encoder.SetValue(protocol.BodyTarget, "stringValue", protocol.QuotedValue{ValueMarshaler: protocol.StringValue(*v.StringValue)}, meta)
86+
}
87+
88+
return nil
89+
}
90+
91+
func MarshalTestOperationInputAWSJSON(v *testOperationInput, e *json.Encoder) []byte {
92+
marshalTestOperationInputAWSJSON(v, &e.Value)
93+
94+
return e.Bytes()
95+
}
96+
97+
func marshalTestOperationInputAWSJSON(v *testOperationInput, j *json.Value) {
98+
object := j.Object()
99+
defer object.Close()
100+
101+
if v.StringValue != nil {
102+
object.Key("stringValue").String(*v.StringValue)
103+
}
104+
105+
if v.IntegerValue != nil {
106+
object.Key("integerValue").Integer(*v.IntegerValue)
107+
}
108+
109+
if len(v.EnumValue) > 0 {
110+
object.Key("enumValue").String(string(v.EnumValue))
111+
}
112+
113+
if v.FloatValue != nil {
114+
object.Key("floatValue").Float(*v.FloatValue)
115+
}
116+
117+
if v.ListValue != nil {
118+
value := object.Key("listValue")
119+
marshalListValueShapeAWSREST(v.ListValue, &value)
120+
}
121+
122+
if v.ShapeValue != nil {
123+
value := object.Key("shapeValue")
124+
marshalNestedShapeAWSREST(v.ShapeValue, &value)
125+
}
126+
127+
if v.MapValue != nil {
128+
value := object.Key("mapValue")
129+
marshalMapShapeAWSREST(v.MapValue, &value)
130+
}
131+
132+
if v.ByteSlice != nil {
133+
object.Key("byteSlice").ByteSlice(v.ByteSlice)
134+
}
135+
}
136+
137+
func marshalListValueShapeAWSREST(v []nestedShape, j *json.Value) {
138+
array := j.Array()
139+
defer array.Close()
140+
141+
for i := range v {
142+
arrayValue := array.Value()
143+
marshalNestedShapeAWSREST(&v[i], &arrayValue)
144+
}
145+
}
146+
147+
func marshalNestedShapeAWSREST(v *nestedShape, j *json.Value) {
148+
object := j.Object()
149+
defer object.Close()
150+
151+
if v.StringValue != nil {
152+
object.Key("stringValue").String(*v.StringValue)
153+
}
154+
}
155+
156+
func marshalMapShapeAWSREST(v map[string]string, j *json.Value) {
157+
object := j.Object()
158+
defer object.Close()
159+
160+
for k := range v {
161+
object.Key(k).String(v[k])
162+
}
163+
}
164+
165+
var testOperationCases = [...]*testOperationInput{
166+
0: {},
167+
1: {
168+
StringValue: aws.String("someStringValue1"),
169+
IntegerValue: aws.Int64(42),
170+
EnumValue: "SOME_ENUM",
171+
FloatValue: aws.Float64(3.14),
172+
ListValue: []nestedShape{
173+
{StringValue: aws.String("someStringValue2")},
174+
{},
175+
},
176+
ShapeValue: &nestedShape{StringValue: aws.String("someStringValue3")},
177+
MapValue: map[string]string{
178+
"someMapKey": "someMapValue",
179+
},
180+
ByteSlice: make([]byte, 1024),
181+
},
182+
}
183+
184+
func BenchmarkEncoderV2(b *testing.B) {
185+
for i, operationCase := range testOperationCases {
186+
b.Run(fmt.Sprintf("Case%d", i), func(b *testing.B) {
187+
encoder := json.NewEncoder()
188+
_ = MarshalTestOperationInputAWSJSON(operationCase, encoder)
189+
})
190+
}
191+
}
192+
193+
func BenchmarkEncoderV1(b *testing.B) {
194+
for i, operationCase := range testOperationCases {
195+
b.Run(fmt.Sprintf("Case%d", i), func(b *testing.B) {
196+
encoder := v1Encoder.NewEncoder()
197+
err := operationCase.MarshalFields(encoder)
198+
b.StopTimer()
199+
if err != nil {
200+
b.Fatal(err)
201+
}
202+
_, err = encoder.Encode()
203+
if err != nil {
204+
b.Fatal(err)
205+
}
206+
})
207+
}
208+
}
209+
210+
func BenchmarkEncoderReflection(b *testing.B) {
211+
for i, operationCase := range testOperationCases {
212+
b.Run(fmt.Sprintf("Case%d", i), func(b *testing.B) {
213+
_, err := reflectEncoder.BuildJSON(operationCase)
214+
if err != nil {
215+
b.Fatal(err)
216+
}
217+
})
218+
}
219+
}

aws/protocol/json/constants.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package json
2+
3+
const (
4+
leftBrace = '{'
5+
rightBrace = '}'
6+
7+
leftBracket = '['
8+
rightBracket = ']'
9+
10+
comma = ','
11+
quote = '"'
12+
colon = ':'
13+
14+
null = "null"
15+
)

aws/protocol/json/encoder.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package json
2+
3+
import (
4+
"bytes"
5+
)
6+
7+
// Encoder is JSON encoder that supports construction of JSON values
8+
// using methods.
9+
type Encoder struct {
10+
w *bytes.Buffer
11+
Value
12+
}
13+
14+
// NewEncoder returns a new JSON encoder
15+
func NewEncoder() *Encoder {
16+
writer := bytes.NewBuffer(nil)
17+
scratch := make([]byte, 64)
18+
19+
return &Encoder{w: writer, Value: newValue(writer, &scratch)}
20+
}
21+
22+
// String returns the String output of the JSON encoder
23+
func (e Encoder) String() string {
24+
return e.w.String()
25+
}
26+
27+
// Bytes returns the []byte slice of the JSON encoder
28+
func (e Encoder) Bytes() []byte {
29+
return e.w.Bytes()
30+
}

0 commit comments

Comments
 (0)