Describe the feature
Allow SDK users to provide a custom JSON decoder implementation for response deserialization, instead of the hardcoded encoding/json.NewDecoder used in generated deserializers.go files.
Currently, every service's generated deserializer creates a stdlib JSON decoder directly:
// From generated deserializers.go (e.g., DynamoDB, S3, SQS)
decoder := json.NewDecoder(body)
decoder.UseNumber()
var shape interface{}
if err := decoder.Decode(&shape); err != nil && err != io.EOF {
return out, metadata, &smithy.DeserializationError{...}
}
This should be configurable via a client option so users can inject a faster, API-compatible JSON decoder.
Use Case
In high-throughput Go services, encoding/json response deserialization becomes a significant source of CPU time and heap allocations. Profiling a service making ~300 DynamoDB BatchWriteItem calls/sec shows
stdlib JSON decoding consuming ~8% of total heap allocations (425MB cumulative over a 60s pprof allocs profile).
Drop-in replacement libraries like github.com/segmentio/encoding/json offer substantially better performance while maintaining full API compatibility with encoding/json:
┌────────────────────┬────────────────────────┬─────────────────────────┬──────────────────────────────┐
│ Operation │ encoding/json │ segmentio/encoding/json │ Improvement │
├────────────────────┼────────────────────────┼─────────────────────────┼──────────────────────────────┤
│ Unmarshal │ 2,400 ns/op, 48 allocs │ 1,200 ns/op, 40 allocs │ 50% faster, 17% fewer allocs │
├────────────────────┼────────────────────────┼─────────────────────────┼──────────────────────────────┤
│ Decode (streaming) │ similar ratio │ similar ratio │ ~50% faster │
└────────────────────┴────────────────────────┴─────────────────────────┴──────────────────────────────┘
We've already replaced encoding/json with segmentio/encoding/json throughout our own codebase and verified correctness + performance under load. However, we cannot extend this optimization to SDK internals
because the import is hardcoded in generated code.
The serialization side already uses the custom smithy-go/encoding/json encoder (not stdlib), so there's precedent for the SDK not relying on encoding/json in the hot path. The deserialization side is the
remaining gap.
Proposed Solution
Define a decoder interface in smithy-go and wire it through as a client option:
// In smithy-go/encoding/json/ (or a new package)
package json
import "io"
// Decoder creates JSON stream decoders from readers.
type Decoder interface {
NewDecoder(r io.Reader) StreamDecoder
}
// StreamDecoder reads and decodes JSON values from a stream.
type StreamDecoder interface {
UseNumber()
Decode(v any) error
}
// StdlibDecoder is the default implementation wrapping encoding/json.
type StdlibDecoder struct{}
func (StdlibDecoder) NewDecoder(r io.Reader) StreamDecoder {
return json.NewDecoder(r)
}
Client option in generated service packages:
// In each service's options.go
client := dynamodb.NewFromConfig(cfg, func(o *dynamodb.Options) {
o.JSONDecoder = myjson.NewDecoder() // custom implementation
})
Generated deserializers.go would change from:
decoder := json.NewDecoder(body)
decoder.UseNumber()
To:
decoder := options.JSONDecoder.NewDecoder(body)
decoder.UseNumber()
Where options.JSONDecoder defaults to StdlibDecoder{} if not configured.
Implementation would require:
- Interface definition in smithy-go runtime
- Codegen change in smithy-go-codegen to emit calls through the interface
- JSONDecoder field in generated Options structs with default
- Regeneration of service packages
I'm happy to contribute PRs if the team is open to this direction — would appreciate guidance on the preferred design before starting.
Other Information
Alternatives considered:
- go.mod replace: Cannot replace stdlib packages via replace directive
- Fork service packages: Would break on every SDK update — not maintainable
- Accept the cost: Viable, but for high-throughput services the allocations are meaningful
Scope:
- This affects all services using awsjson1.0 and awsjson1.1 protocols (DynamoDB, SQS, SNS, Lambda, etc.)
- The same interface could optionally be extended to serialization, but the custom smithy-go encoder already performs well there
Compatible drop-in libraries:
- github.com/segmentio/encoding/json — implements full encoding/json API surface
- github.com/goccy/go-json — same
- github.com/bytedance/sonic — same (with JIT on amd64)
Acknowledgements
AWS Go SDK V2 Module Versions Used
github.com/aws/aws-sdk-go-v2 v1.41.1
github.com/aws/aws-sdk-go-v2/config v1.32.7
github.com/aws/aws-sdk-go-v2/credentials v1.19.7
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.54.0
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6
github.com/aws/smithy-go v1.24.0
Go version used
1.26.1
Describe the feature
Allow SDK users to provide a custom JSON decoder implementation for response deserialization, instead of the hardcoded encoding/json.NewDecoder used in generated deserializers.go files.
Currently, every service's generated deserializer creates a stdlib JSON decoder directly:
This should be configurable via a client option so users can inject a faster, API-compatible JSON decoder.
Use Case
In high-throughput Go services, encoding/json response deserialization becomes a significant source of CPU time and heap allocations. Profiling a service making ~300 DynamoDB BatchWriteItem calls/sec shows
stdlib JSON decoding consuming ~8% of total heap allocations (425MB cumulative over a 60s pprof allocs profile).
Drop-in replacement libraries like github.com/segmentio/encoding/json offer substantially better performance while maintaining full API compatibility with encoding/json:
We've already replaced encoding/json with segmentio/encoding/json throughout our own codebase and verified correctness + performance under load. However, we cannot extend this optimization to SDK internals
because the import is hardcoded in generated code.
The serialization side already uses the custom smithy-go/encoding/json encoder (not stdlib), so there's precedent for the SDK not relying on encoding/json in the hot path. The deserialization side is the
remaining gap.
Proposed Solution
Define a decoder interface in smithy-go and wire it through as a client option:
Client option in generated service packages:
Generated deserializers.go would change from:
To:
Where options.JSONDecoder defaults to StdlibDecoder{} if not configured.
Implementation would require:
I'm happy to contribute PRs if the team is open to this direction — would appreciate guidance on the preferred design before starting.
Other Information
Alternatives considered:
Scope:
Compatible drop-in libraries:
Acknowledgements
AWS Go SDK V2 Module Versions Used
github.com/aws/aws-sdk-go-v2 v1.41.1
github.com/aws/aws-sdk-go-v2/config v1.32.7
github.com/aws/aws-sdk-go-v2/credentials v1.19.7
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.54.0
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17
github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6
github.com/aws/smithy-go v1.24.0
Go version used
1.26.1