JSON vs YAML vs XML: differences, trade-offs and conversion pitfalls
The three formats are not interchangeable. What each represents natively, where conversion silently loses information, and the parsing risks only two of them carry.
Published
Convert some XML to JSON and back, and you will not always get what you started with. That is not a bug in the converter — it is the direct consequence of three formats that model data differently being treated as interchangeable.
Knowing exactly where they diverge tells you when a conversion is safe and when it quietly loses something.
What each is actually for
JSON is a data-interchange format with a deliberately tiny grammar: objects, arrays, strings, numbers, booleans and null. No comments, no schema mechanism, no way to annotate a value. That minimalism is why it won for APIs — there is almost nothing to disagree about.
YAML is a configuration format designed to be written by humans. It is a superset of JSON, adds comments, and replaces punctuation with indentation. Its flexibility is both the appeal and the source of its reputation for surprises.
XML is a document markup language that also gets used for data. It carries things the other two have no native concept of: attributes, namespaces, mixed content, processing instructions, and a mature schema ecosystem.
Where conversion loses information
Comments exist only in YAML. Convert YAML to JSON or XML and every comment is gone; convert back and they do not return. For a configuration file that documents itself in comments, this is usually the most damaging loss.
Attributes exist only in XML. An element can have an attribute and text
content at the same time, and JSON has no equivalent. Every converter invents a
convention — commonly prefixing attribute keys with @ and putting text under
#text. That is a convention, not a standard, which is why the
converter here lets you change both.
Arrays versus repeated elements is the subtlest trap. XML has no array type, only elements that may repeat:
<list><item>a</item></list>
becomes {"list": {"item": "a"}}
<list><item>a</item><item>b</item></list>
becomes {"list": {"item": ["a","b"]}}
The same structure produces an object in one case and an array in the other, purely because of how many children happen to be present. Code consuming the JSON has to handle both — and the single-item case is exactly the one that slips through testing.
Namespaces can be preserved as prefixed key names or stripped, but either way the namespace semantics are gone once in JSON: the prefix becomes an ordinary part of a string.
Types. YAML’s type inference is eager. Unquoted yes, no, on and off
were booleans in YAML 1.1, which produced the famous Norway problem — the
country code NO parsing as false. YAML 1.2 parsers treat these as strings,
but behaviour depends on the parser, so quoting anything that must stay a
string remains good practice. In XML everything is text until something decides
otherwise, so a port number may arrive as a number or a string depending on the
converter’s settings.
Whitespace is significant inside XML elements in ways JSON does not model, so mixed content — text and elements interleaved in a paragraph — converts poorly or not at all.
The security difference
This is where the three genuinely diverge in risk, and it is worth knowing which of them can hurt you.
XML external entities (XXE) are the classic attack. XML lets a document declare entities, and an entity can reference an external resource — a local file path, or a URL on an internal network. A parser that resolves external entities will read that resource and place its contents in the parsed output: a straightforward file-disclosure vulnerability. The related billion laughs attack nests entity definitions so expansion grows exponentially and exhausts memory.
The defence is to refuse external entities, which the parser used here does unconditionally — entity declarations are rejected rather than resolved. There is an automated test asserting exactly that, so a dependency update cannot weaken it silently.
Unsafe YAML is the other one. YAML tags can instruct a parser to
instantiate a language-specific type, and parsers honouring this can be made to
construct arbitrary objects — the reason older PyYAML’s load was a remote
code execution risk and safe_load exists. The converter here uses the core
schema, so tags resolve to ordinary strings and arrays and nothing is
constructed or executed. Also covered by a test.
JSON has no equivalent hazard. Its grammar has no mechanism to reference external resources or instantiate types. Parsing untrusted JSON is safe; the risks with JSON are downstream, in what you do with the values.
Choosing between them
- APIs and data interchange: JSON. Universal support, no ambiguity, no parsing hazards.
- Configuration humans edit: YAML, for the comments — with values quoted wherever type inference could bite.
- Documents, or where schema validation and namespaces matter: XML. It is verbose because it is doing more.
When converting, the safe habit is to check rather than assume: convert, convert back, and compare. Where the round trip is not clean, the difference is telling you something real about the two formats rather than about the tool.