Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

JSON parsing

The SDK uses simdjson 3.0+ for JSON syntax, UTF-8 validation, SDK message decoding, and transport envelopes. This is a required core dependency even with A2A_ENABLE_SIGNING=OFF. OpenSSL remains optional. No public SDK signature exposes simdjson types. Protobuf continues to serialize messages.

Parsing inventory

Entry pointImplementation and remaining conversions
core::JsonToMessageShared simdjson validation followed by reflection-based decoding of ordinary SDK fields. Both protobuf and JSON field names work. Unknown-field and strict-null/duplicate options are preserved.
Agent Card discovery (including discovery for gRPC)Shared decoder; the verifier still receives the original, complete JSON before typed decoding, preserving unknown signed fields. Native gRPC messages use protobuf wire decoding.
HTTP+JSON requests, responses and errorsExisting binding validation and error tagging call the shared decoder; google.rpc.Status and metadata remain protobuf messages.
JSON-RPC client unary results, errors and SSEShared envelope parser; the original result JSON is decoded directly. Removed intermediate protobuf Struct/Value, result replacement, and separate ListTasks/SSE scanners.
JSON-RPC server requests and push configurationsShared envelope parser; original params JSON reaches the shared decoder. Removed the transport’s flat-message reflection parser. IDs retain their existing protobuf Value representation for outgoing envelope serialization.
JSON-RPC ListTasks binding parametersShared simdjson-to-Struct conversion followed by the existing binding-specific filters and validation. This Struct remains to preserve binding errors and legacy parameter semantics.
HTTP+JSON and JSON-RPC SSE event bodiesShared message decoder. SSE framing remains in SseParser, which parses event framing rather than JSON.
Push-notification configuration payloadsShared decoder through each binding; delivery serializes task payloads and does not parse callback bodies.
Struct, Value, ListValuesimdjson-to-protobuf conversion; these objects are the actual application data model, rather than an intermediate representation of a typed message. Numbers retain protobuf Value’s binary64 semantics.
Bytes, uncommon numeric scalar types, non-string map keys and well-known messagesProtobuf’s semantic decoder on original JSON tokens. This deliberately retains base64 variants, exact int64/range rules, timestamps, durations, wrappers, Any and FieldMask behavior without reimplementing those codecs. Bytes use the protobuf wrapper decoder only for that field; uncommon numeric types and non-string maps use the fallback for their message subtree. Quoted/exponent int32 tokens use the protobuf wrapper decoder. Null-bearing typed messages retain protobuf decoding because supported protobuf versions differ on null container entries; strict SDK validation runs first.
Outgoing JSON-RPC serializationExisting protobuf Value bridges remain where envelope writers require them; parsing these generated payloads uses the shared implementation. Serialization redesign is outside this migration.
Signed Agent CardsThe existing signing parser and dedicated RFC 8785 serializer remain. simdjson does not implement JCS; general parsing never reconstructs the input used for signature verification.

The former public structural range helper a2a/core/json_value.h was removed with its custom grammar, tests and scanner-only benchmarks. SDK transports no longer need it.

Ownership and input policy

Each internal document exclusively leases a DOM parser from a thread-local cache. Nested/reentrant parses acquire separate parsers; there is no process-wide lock. The on-demand cursor owns padded input and its own parser. DOM storage and cursor views never escape into SDK results or application callbacks. Returned protobuf messages and envelope JSON strings own their data. Each cache retains at most one parser with input capacity up to 1 MiB. Larger documents remain supported, but their DOM and on-demand parsers are released after use instead of retaining their large buffers on worker threads.

The complete input is validated before decoding or skipping unknown fields. A second on-demand pass reads original tokens for typed payloads and subtree fallbacks, avoiding binary64 rounding of decimal integers during extraction. JSON-RPC envelopes must be objects and reject duplicate decoded member names, including escaped spellings. Typed strict parsing also rejects duplicate known fields/aliases in nested messages. Ordinary typed decoding keeps the last occurrence of repeated and message fields, replacing earlier arrays or objects even when the last occurrence is empty. Ordinary message fallbacks reject duplicate known fields instead of depending on version-specific merging. Protobuf data maps retain their normal map semantics. Binding-specific null restrictions remain in place; ordinary protobuf nulls leave fields unset.

Inputs are limited to 64 MiB and nesting depth 128. Malformed JSON, trailing data, invalid UTF-8 and numbers outside simdjson’s supported numeric range return Error::Serialization; transports retain their HTTP status, transport tag and binding-specific error mapping. Protobuf numeric fallback receives original tokens, so rounding cannot make an invalid integer acceptable. Signing retains its own stricter JCS number policy and existing limits.

Validation and measurements

The shared golden/parity fixtures exercise aliases, enums, int32/exponent limits, int64 precision, bytes, timestamps, metadata, nulls and oneofs. Existing transport, streaming, discovery and signing suites exercise the migrated entry points. Cross-binding scenarios include binary parts and null/list metadata.

The parsing benchmarks include small messages, Agent Cards, task lists, SSE event messages, and a task with 256 additional history messages and a 16 KiB binary artifact. Client and server benchmarks measure binding processing separately from parser-only timing. One Linux sample compared bed242d with this migration using GCC 15.3, protobuf 3.21.12 and simdjson 3.12.3, Release builds with signing disabled, and identical Google Benchmark definitions. Values are median CPU microseconds from three repetitions with a 0.1-second minimum per repetition on a shared host. These are indicative measurements, rather than cross-platform guarantees.

WorkloadBefore (µs)After (µs)
Small SendMessage request12.781.43
Agent Card21.142.00
SSE StreamResponse payload21.203.84
Large task history/artifact (290,486 bytes)2,035.36339.05
HTTP+JSON client ListTasks, 200 tasks638.64629.12
JSON-RPC client ListTasks, 200 tasks876.59621.81
JSON-RPC server GetTask, in-process binding/dispatch110.2242.79

A separate warmed allocation probe counted C++ new/new[] calls: small message 305 → 10, Agent Card 487 → 25, SSE payload 479 → 60, large task 5,161 → 1,668, and mocked JSON-RPC GetTask 1,313 → 233. This excludes direct malloc calls and retained parser buffers. Whole-process peak RSS was approximately 13 MiB for both probe executables, so these results do not establish lower resident memory.

The required network performance smoke check produced 54 result rows with zero errors. It is a correctness smoke check; parser-only and in-process timings do not establish network latency or throughput improvements. Full native validation passed 804 configured CTest checks with two expected TLS skips. Unmodified upstream TCK runs across all three transports and both stores each passed 249 tests with 16 conditional skips and no failed assertions. Upstream reports 80.9% mandatory / 80.0% overall compliance because 25 registry requirements have no executable fixtures; these gaps remain explicit in the PR validation report.