Track 1 / Guide 02
JSON vs XML vs YAML: Data Serialization
Compare JSON, XML, and YAML with reproducible payload and parsing measurements, explicit data models, and operational tradeoffs for services and configuration.
On this page
Compare equivalent models before comparing bytes
A serialization benchmark is meaningful only when every representation carries the same information. Comparing a compact JSON object with heavily indented XML or verbose YAML mainly measures formatting choices. Start with a logical record: an identifier, a boolean, a decimal amount represented according to a shared contract, and a sequence of tags. Specify how each format represents null, empty collections, repeated fields, and names that are not valid XML element names.
XML has elements, attributes, namespaces, and mixed text content. JSON has a smaller value model with explicit arrays. YAML supports readable mappings and sequences but also has tagging and alias concepts that require a loader policy. A naive conversion can discard distinctions rather than simply change punctuation. Declare whether round-trip fidelity means preserving values, document structure, comments, ordering, or exact original text.
For an API comparison, choose a deliberately narrow model and implement an adapter from each parsed representation to that model. Measure adapter cost separately and together with parsing. A fast XML parser producing a tree is not directly comparable to a JSON parser producing ready-to-use application objects if the XML tree still needs substantial traversal. The JSON specification guide explains the runtime distinctions that remain even after a JSON parse succeeds.
Measure payload overhead without false precision
Repeated field names increase text payload size, but compression can eliminate much of that repetition across a sufficiently large message. Small messages behave differently because framing and compression headers become a larger fraction of the total. Report both uncompressed and compressed byte counts, and specify the compression level. Do not report character counts as network size when the payload contains non-ASCII text.
Compare compact encodings for transport and readable encodings for operator workflows as separate scenarios. Whitespace can improve reviewability while adding bytes that are cheap to compress. XML closing tags create repetition that often compresses well. YAML indentation can be concise but is not a general guarantee of smaller data. Long values may dominate all three formats, making punctuation differences operationally irrelevant.
Keep message boundaries realistic. Compressing ten thousand records as one document can exaggerate savings compared with ten thousand independent requests. Conversely, measuring tiny isolated messages can understate compression benefits in batch exports. Capture total HTTP or RPC traffic separately from serialized body size because headers, TLS records, and connection reuse also affect the system. If a format changes batching behavior, evaluate that architectural choice explicitly rather than attributing the entire improvement to serialization syntax.
A reproducible local benchmark
The program below creates the same collection in JSON, XML, and YAML, reports UTF-8 and gzip sizes, and samples parser latency. Install PyYAML in a virtual environment, save the program as formats.py, and run it with Python 3. It uses safe_load for YAML, generates its XML locally, and excludes network and disk input from timing. The numbers are measurements of your environment, not published universal rankings.
The parsed XML result is still a tree, while the other parsers return mappings and lists. That difference is intentional and must appear beside the results. Add the XML-to-record adapter if application-ready objects are the actual comparison target. Avoid printing parsed structures inside the timed loop, because terminal output can dominate the work being investigated.
Run several processes rather than trusting one sequence of samples. Record Python, PyYAML, operating system, processor, and whether optional native accelerators are active. Use both a small response and a larger batch. The median helps summarize repeated measurements, but individual request latency at service load requires a different experiment. Keep raw samples so outliers can be examined instead of silently removed. A benchmark that cannot be reproduced from its fixtures and dependency versions is weak evidence for an architectural migration.
import gzip
import json
import statistics
import timeit
import xml.etree.ElementTree as ET
import yaml
records = [{"id": i, "name": "sample", "active": True}
for i in range(1000)]
root = ET.Element("records")
for record in records:
node = ET.SubElement(root, "record")
for key, value in record.items():
ET.SubElement(node, key).text = str(value).lower() if (
isinstance(value, bool)) else str(value)
texts = {
"json": json.dumps(records, separators=(",", ":")),
"xml": ET.tostring(root, encoding="unicode"),
"yaml": yaml.safe_dump(records, sort_keys=False),
}
parsers = {"json": json.loads, "xml": ET.fromstring,
"yaml": yaml.safe_load}
for name, text in texts.items():
raw = text.encode("utf-8")
samples = timeit.repeat(
lambda: parsers[name](text), repeat=7, number=20)
median_ms = statistics.median(samples) * 1000 / 20
print(name, "bytes=", len(raw),
"gzip=", len(gzip.compress(raw, mtime=0)),
"median_ms=", round(median_ms, 3))Parsing latency is only one budget
A microservice spends time receiving bytes, decompressing, decoding characters, parsing structure, validating a contract, authorizing an operation, and accessing storage. Reducing parsing time has little impact when database waits dominate. Measure the fraction of total request time spent in each phase before changing a format. Also examine throughput and CPU saturation: a small per-request saving may matter at high volume even when median latency barely changes.
Memory allocation can reverse an attractive timing result. A parser that builds a complete tree may retain more data than a streaming reader extracting only a few fields. Compare peak resident memory, allocation volume, and object lifetime under concurrency. Parsing one message repeatedly on a quiet machine does not model hundreds of simultaneous requests retaining decoded objects while awaiting database connections.
Separate cold initialization from steady state. Schema compilation, module import, native extension loading, and JIT warmup may be important for short-lived workers but irrelevant for a long-running service. Include these phases when evaluating serverless or command-line workloads. For CPU-heavy transport paths, the Protobuf comparison extends the method to generated binary codecs and explains why a codec benchmark cannot establish an RPC framework's end-to-end performance.
Configuration and transport have different readers
Configuration is edited by people and interpreted repeatedly by software. Comments, anchors, and readable nesting can make YAML useful for maintained deployment files, but implicit typing and parser-version differences can surprise operators. Choose a constrained schema, quote ambiguous scalar values, and validate the rendered configuration after template expansion. A syntactically valid source template does not establish that the final document is valid.
Transport data is usually generated, so human editing convenience should receive less weight than predictable parsing and compatible clients. JSON is often a pragmatic external API choice because common runtimes already provide parsers. XML remains appropriate when namespaces, mixed content, document schemas, or established partner protocols are essential. Replacing an XML document contract with JSON can require an application redesign rather than a serializer swap.
Consider who debugs failures. A support engineer may need to inspect a response without generated client code, while a tightly controlled internal service fleet can tolerate specialized tooling. Measure that operational burden in the decision record. Readability is not purely a punctuation preference: stable field names, bounded nesting, consistent errors, and documented semantics make any supported format easier to work with. A concise payload with ambiguous values can be harder to operate than a larger but explicit representation.
Safe parser configuration is part of the format
Do not benchmark unsafe parser settings and then deploy them because they appear faster. XML entity and external-resource behavior must be configured for untrusted input, using a hardened parser appropriate to the runtime. YAML should use a safe loader that does not instantiate arbitrary application objects. Safe loading is necessary but does not remove the need for byte, depth, and work limits.
JSON lacks XML-style entity declarations, but it can still consume excessive memory through large strings, many properties, or extreme nesting. A format without executable tags is not automatically safe for every downstream use. Values can become dangerous when concatenated into SQL, inserted into HTML, or merged into privileged application configuration. Treat parsing as a structural boundary, followed by explicit validation and authorization.
Check decompression limits independently of compressed request size. A small compressed body can expand into a much larger document before the parser sees it. Apply limits at the gateway and application with clear ownership of compressed and decompressed byte counts. Add timeouts and bounded queues so slow or expensive requests do not monopolize worker capacity. For all three formats, include rejection behavior in the benchmark and record whether errors are controlled responses or process-level failures.
Conversion requires a written mapping
Before exporting JSON as XML, define an encoding for arbitrary object keys and arrays. Keys may contain spaces, start with digits, or use characters unsuitable for element names. Converting them directly into markup creates malformed output or collisions after sanitization. A generic mapping with explicit name attributes can preserve the information, although it may be less convenient than a domain-specific schema.
For YAML output, specify the supported version and loader family. Quoting strings that resemble booleans, dates, or numbers reduces accidental type changes in downstream tools. Preserve null separately from an empty string, and test arrays containing mixed types if the application permits them. Round trips should be tested against values rather than the cosmetic arrangement of indentation and quotes.
Cross-platform text handling deserves its own fixtures. Include escaped quotes, non-Latin names, supplementary characters, and line endings. The Unicode compatibility guide explains why decoding bytes incorrectly can corrupt data before any format-specific parser runs. When a conversion cannot preserve a feature, fail explicitly or expose the limitation in the export contract. Silent loss of attributes, comments, tags, or numeric precision is not a successful transformation merely because the resulting text parses.
Make the migration decision measurable
Write an acceptance threshold before running the experiment. Examples include reducing CPU at the existing throughput, keeping tail latency below a service objective, preserving every contract fixture, and maintaining acceptable operator tooling. Avoid selecting whichever metric happens to favor a preferred format after measurements arrive. The decision should explain why a particular constraint matters to this service.
Deploy a format change through explicit negotiation or versioning rather than changing an existing media type's meaning. Keep both readers during the transition only if their validation and authorization policies remain equivalent. Instrument adoption and failures by format and client version. A compatibility adapter should have an owner and retirement criterion so it does not become an indefinite source of duplicate behavior.
Finally, retain the benchmark fixtures, dependency lock files, raw samples, and mapping rules with the design record. Repeat the experiment when payload shape or runtime versions change substantially. A conclusion such as JSON is the simplest compatible option for this external API is defensible without claiming it wins every latency test. Likewise, choosing XML for a document protocol or YAML for reviewed configuration can be correct even when its parser is slower in an unrelated microbenchmark.
Include schema and adapter cost in procurement decisions
A format choice affects tooling beyond the parser. Inventory schema validators, code generators, observability adapters, and partner SDKs already used by the organization. Replacing a format can remove one conversion while adding several others at integration boundaries. Count those transformations in the design rather than treating them as work for another team.
Prepare an equivalence test that reconstructs the logical record from every candidate representation and compares all fields. For decimals, use the agreed decimal or string representation rather than float equality with an arbitrary tolerance. For arrays, preserve order unless the domain explicitly defines a set. For XML, decide whether attributes and elements are semantically interchangeable in the selected mapping.
Also test operator workflows under failure. Ask whether a support engineer can identify an invalid field from the error response, whether a deployment diff preserves meaningful configuration changes, and whether a generated client can be upgraded without touching unrelated services. These are concrete maintenance costs, even though they are not expressed in nanoseconds.
Use a decision table with required capabilities, measured costs, and unresolved assumptions. Mark an unsupported capability as a constraint rather than assigning it an invented performance penalty. This makes the final choice auditable: a team can see that a format was selected because it supports required document semantics or simplifies the actual client fleet, not because one benchmark happened to produce a smaller number.
Engineering Comparison
| Dimension | JSON | XML | YAML |
|---|---|---|---|
| Value model | Objects and arrays | Elements, attributes, mixed content | Mappings, sequences, tags |
| Transport readability | Direct API inspection | Verbose but explicit structure | Compact indentation |
| Type policy | Small built-in value set | Schema or application mapping | Loader and schema dependent |
| Compression | Repeated names compress | Repeated tags compress | Layout also compresses |
| Safe ingestion | Size and structural limits | Harden entities and external access | Safe loader plus resource limits |
| Typical fit | Web APIs and tooling | Document and partner protocols | Human-maintained configuration |
The table compares capabilities, not universal speed rankings. Use the executable experiment and an application-level adapter to measure the workload you intend to deploy.