Skip to content
datahacks.dev
XML

XML Versus JSON Versus YAML for Configuration Files

One small config written three ways: size, comments, types and the YAML country code that parses as False. A decision guide for choosing between them.

5 min read
Summarize in AI
Flat illustration of two options weighed side by side, with the XML and JSON logos.

Configuration formats attract opinions, and the opinions are usually about taste. The format matters less than people think, and the properties that do matter are specific: can a person comment it, will the parser change a value without telling you, can a machine validate it and who has to edit it at 3 a.m. This post puts the same small configuration in all three formats, shows what a parser does with it, and then reduces the decision to a few questions.

The same configuration, three ways

The settings: a server with a host and a port, a list of two features, a country code and a timeout.

config.xmlXML
<config>
  <!-- comment survives in the file -->
  <server host="db1" port="5432"/>
  <features><feature>search</feature><feature>export</feature></features>
  <country>NO</country>
  <timeout>30</timeout>
</config>
config.yamlYAML
# comment survives in the file
server: {host: db1, port: 5432}
features: [search, export]
country: NO
timeout: 30
config.jsonJSON
{"server":{"host":"db1","port":5432},"features":["search","export"],"country":"NO","timeout":30}

Sizes, as written above: JSON 96 bytes, YAML 114 bytes, XML 215 bytes. XML is over twice the size of JSON, because every value has a name written twice. For a config file that is irrelevant: nobody has ever lost a race over 119 bytes. What the difference does show is how much ceremony each format asks of the person typing.

What the parser does with it

Load each and look at the types:

Text
yaml country: False
yaml timeout: 30
json timeout: 30
xml timeout: '30' port attr: '5432'

Three behaviors, and one is a trap.

**YAML turned NO into False.** In YAML 1.1, which PyYAML implements, an unquoted NO, No, no, off and a few others are booleans. The country code for Norway is a famous victim (the Norway problem and YAML boolean surprises), and the program reads a Boolean where it expected a string, usually far from where the file was edited. The cure is quoting (country: "NO") or a schema, and neither is automatic.

XML returns text for everything. timeout is the string '30' and port is '5432'. That is safe, since nothing is silently converted, and it means your code does the conversion, or a schema does. A mistyped timeout of 3O (letter O) reaches your int() call and fails there, where you can name the setting.

JSON has types and is strict. 30 is a number, "NO" must be quoted to be a string, and a trailing comma is a parse error. The strictness catches mistakes at load time. It is also why people dislike hand-editing it.

And comments: JSON has none.

Python
json.loads('{"a":1 /* c */}')
Text
Expecting ',' delimiter: line 1 column 8 (char 7)

JSON parsers reject comments because the specification has none (RFC 8259 defines the grammar). Variants exist (JSONC, JSON5, HJSON), each requiring a parser that understands it, and each is a different format that looks like JSON until it breaks something. For files read by tools you do not control, such as a VS Code settings file that allows comments, check what the specific reader accepts.

The decision, by question

Who edits it? If humans edit by hand and often, YAML or XML. People forgive neither JSON's missing comments nor its commas. If the file is generated or edited through a tool, JSON is fine and has the least ambiguity.

Does it need comments? Configuration that people maintain needs "why is this 30?" next to the 30. That rules out plain JSON.

Must it be validated? XML has XSD and RELAX NG, mature and standardized; JSON has JSON Schema, widely supported; YAML is usually validated with JSON Schema applied to the parsed data (validating config YAML with JSON Schema). All three can be checked. XML's tooling is the oldest, and editors can offer completion from a schema.

What does the ecosystem already read? This is often the whole decision. Maven reads XML, Kubernetes reads YAML, npm reads JSON. Fighting the ecosystem's format costs more than any property of the format.

Does it have structure that needs composition? XML has namespaces and XInclude for assembling a configuration from parts. YAML has anchors and merge keys (YAML anchors, aliases and merge keys). JSON has nothing, and tools bolt on $ref.

How tolerant must it be of mistakes? A YAML indentation error can change meaning without a parse error. XML's explicit end tags make that class of mistake loud. JSON's braces do too.

A short recommendation

For a new application, my default is YAML or TOML for files people edit (with a schema and strict quoting for strings that look like other types), JSON for files machines exchange and for APIs, and XML only when the surrounding tools expect it or when the configuration is a document with structure that benefits from a schema, such as a build definition or a workflow. That is a preference, not a rule, and the exceptions are common.

Whatever you pick, apply two habits. Validate on load and fail with the key's name, not a stack trace. And quote or type values that could be misread: in YAML, strings that look like booleans, numbers, dates or nulls (why a number turns into something else in YAML).

The cluster hub is choosing XML over the alternatives, or not. The YAML side of the comparison is in YAML versus JSON for configuration, and the version differences in YAML 1.1 versus 1.2. Conversions between the formats are covered in converting XML to YAML for config review. The YAML specification is at yaml.org, and XML Schema at the W3C. Convert a snippet with the YAML to JSON tool.

When none of the three is the right config format

The configuration is code. If it needs conditionals, loops or computed values, a real language (Python, Lua, a restricted subset) is better than a data format with embedded templating. Helm's templated YAML is the cautionary tale.

TOML or INI fits. For flat or lightly nested settings edited by hand, TOML has comments, explicit types and no indentation sensitivity. Python's packaging and Rust's Cargo chose it for that reason.

The settings change often at runtime. Feature flags and tunables that operators change daily belong in a database or a configuration service with an audit trail, not in a file under version control.

You need binary or very large data. None of the three is suited to it (embedding binary data in XML).

Also inYAML
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.