Guide
JSON vs YAML: Choosing the Right Config Format
One is verbose and predictable. The other is concise and has opinions about Norway. Pick for the right reason.
By Buğra SözeriPublished
JSON and YAML look like the same thing in different costumes until you try to maintain a thousand-line config file in either. The differences matter, and most of them aren’t about syntax — they’re about what each format does when you’re slightly wrong.
Same example in both
The shortest way to feel the difference is to read identical data side by side.
JSON
{
"name": "convertitive",
"version": "1.4.0",
"ports": [80, 443],
"features": {
"darkMode": true,
"telemetry": false
},
"owners": ["[email protected]", "[email protected]"]
}YAML
name: convertitive
version: 1.4.0
ports:
- 80
- 443
features:
darkMode: true
telemetry: false
owners:
- [email protected]
- [email protected]The YAML is about 30% shorter and arguably more readable to a human who’s never seen either before. The JSON is unambiguous to a machine, parser-friendly, and survives copy-paste through any text editor on any operating system without invisible character damage.
That summary is most of the choice, but the details underneath it are where projects get into trouble.
What YAML wins at
Comments
YAML supports # line commentsanywhere. JSON doesn’t — RFC 8259 deliberately excludes them. For a config file that humans will read and edit, comments aren’t a nice-to-have; they’re how you document why a flag is off or what value range a setting actually accepts.
# pin to a specific patch release; do not auto-update
version: 1.4.0
ports:
- 80
# 443 is here so the LB health check works; do not remove
- 443JSON5 and JSONC add comment support but are not JSON — a strict parser will reject them. If your config will be consumed by code you don’t control, that’s a deal-breaker.
Multi-line strings
YAML has two block-scalar styles for multi-line strings. | preserves newlines, > folds them into spaces. Both let you write paragraph-length values without escape soup:
description: |
Convertitive is a tools site.
It loads fast and has no popups.
summary: >
A single sentence written across
multiple lines for editor comfort.The JSON equivalent is one long line with \n escapes, which is fine for one field and miserable for fifty.
Anchors and aliases
YAML lets you label a node with & and reference it elsewhere with *. Merge keys (<<) extend a mapping with another. The result is real DRY in a config file:
defaults: &defaults
retries: 3
timeout_ms: 5000
production:
<<: *defaults
endpoint: api.example.com
staging:
<<: *defaults
endpoint: staging.api.example.com
timeout_ms: 30000 # overrides the defaultJSON has nothing equivalent. The workaround is either templating (Jinja, Helm) or post-processing at load time, both of which add machinery JSON-native configs avoid.
Visual scanability
Indentation-as-structure is the single biggest reason teams pick YAML. A deeply nested document reads top-to-bottom; you don’t lose your place counting braces. That’s why every Kubernetes manifest is YAML and every Ansible playbook is YAML.
What JSON wins at
Unambiguous parsing
JSON has a five-page grammar. Two correct parsers will agree on every document. YAML has a ninety-page grammar with multiple valid interpretations of the same document depending on parser version. JSON’s strictness is the feature.
Tooling ubiquity
Every language ships with a JSON parser in its standard library. YAML requires a third-party dependency in most environments — PyYAML, js-yaml, snakeyaml, go-yaml. For a config file embedded in a tool that has to run anywhere, JSON’s zero-dependency story matters.
Round-trips through any text channel
JSON has no significant whitespace. You can paste it into a chat client, an email, a shell prompt; you can minify it; you can flow it across an HTTP body without thinking. YAML is significant-whitespace and a single tab vs spaces mistake breaks the document.
The Norway problem — literally
The most famous YAML footgun: in YAML 1.1, the string NO parses as the boolean false. Country lists that include Norway’s ISO code (NO) become [false, ...] unless quoted. The same happens with YES, ON, OFF, and various capitalisations. YAML 1.2 fixes this by treating only lowercase true and falseas booleans, but a startling amount of tooling defaults to 1.1 in 2026 — the Python yamlmodule’s default loader, for example, is YAML 1.1.
JSON has no equivalent surprise. “NO“ is a string. trueis a boolean. There’s no version ambiguity to inherit.
Security defaults
YAML’s spec includes type tags that let a document instruct the parser to instantiate arbitrary classes. Several language bindings honoured this by default for years, which means loading an attacker-controlled YAML document could execute arbitrary code — the same class of bug as Java deserialization. The fix is to use a “safe” loader (yaml.safe_load in Python, YAML.safe_load in Ruby) which only emits primitive types.
JSON has no such mechanism. The worst a malicious JSON document can do is crash the parser with a deeply nested structure or exhaust memory with a long string. Both are easier to defend against than RCE.
Shared gotchas
JSON number precision
JSON’s spec says numbers can be of arbitrary magnitude and precision. JavaScript’s JSON.parse and most non-JS implementations actually deserialize them into IEEE 754 doubles, which can represent integers exactly only up to 2^53 - 1 (about 9.007 * 10^15). A Twitter snowflake ID, a Stripe charge ID, or any 19-digit integer will round silently. The fix is to encode large integers as strings (Twitter, Discord, and Stripe all do this in their JSON APIs) or use a parser with BigInt support.
YAML inherits the same problem in any language that deserializes integers into a fixed-width type. The mitigation is the same: quote large integers if accuracy matters.
YAML version drift
YAML 1.1 (2005) and YAML 1.2 (2009) are the two versions in the wild. 1.2 is a strict superset of JSON, fixed the Norway problem, and tightened the spec considerably. Many parsers still default to 1.1 silently. The first thing to do with a new YAML codebase is establish which version the parser is using and write the answer in the README.
Duplicate keys
JSON’s spec doesn’t say what to do with duplicate keys; parsers disagree (most take the last value, some throw, some return both). YAML’s spec says duplicates are an error but many parsers tolerate them. In both, never write duplicate keys, and validate with a schema if your input is untrusted.
A decision tree
For new projects where you control the consumer:
- Machine-to-machine wire format?Use JSON. Every language parses it, comments don’t matter, and the strictness eliminates one whole class of integration bug.
- Human-edited config under 100 lines? Use YAML if the consumer expects YAML, TOML otherwise. Both support comments; TOML is less surprising.
- Human-edited config over 100 lines, deeply nested?YAML’s anchors and block scalars start to pay rent at that size. Pin to YAML 1.2 in the documentation.
- Untrusted input? JSON is safer by default. If you must accept YAML, use the safe loader and a schema.
- Consumer is fixed (Kubernetes, npm)?Match the consumer. Don’t fight the ecosystem.
Converting between them
The conversion is mechanical because YAML 1.2 is a JSON superset. JSON in, YAML out is always lossless. YAML in, JSON out is lossless only if the YAML uses primitive types and no anchors/aliases that would require expansion.
Our JSON ↔ YAML converter handles both directions in the browser and shows you the line where parsing fails if the input is invalid. For pure JSON work — pretty-printing, validating, minifying — the JSON formatter is a sharper tool.
The honest takeaway
JSON is the right default for anything two programs need to agree on. YAML is the right default for anything a human will edit at length. Most production systems end up with both: YAML at the human boundary (Helm charts, GitHub Actions workflows, application config), JSON over the wire and at rest in databases.
The wrong answer is to pick whichever format you saw first and use it for everything. JSON for a thousand-line configuration with no comments is hostile. YAML for an HTTP response body invites every gotcha in this article. Match the format to the consumer and the editor, and reread the Norway story once a quarter so you stay paranoid.
Frequently asked questions
- Is JSON a subset of YAML?
- YAML 1.2 was explicitly designed to be a strict superset of JSON, so any valid JSON document is also valid YAML. YAML 1.1, which is still the default in a surprising amount of tooling (including older PyYAML), is not — it parses some JSON-valid strings differently. If you need the superset guarantee, confirm your parser is YAML 1.2.
- Why does YAML parse 'no' as false?
- YAML 1.1's boolean rules treat 'yes', 'no', 'on', 'off', 'true', 'false', 'y', 'n' (and various capitalisations) as booleans. A country list with 'NO' (Norway's ISO code) becomes [false, ...]. YAML 1.2 fixed this — only 'true' and 'false' are booleans. If you're stuck on a YAML 1.1 parser, quote ambiguous strings.
- Can JSON have comments?
- Not in the standard. RFC 8259 explicitly excludes comments. Workarounds: JSON5 and JSONC add comment support but are not interoperable with strict JSON parsers, or include a sibling key like '_comment' that downstream code ignores. If comments are non-negotiable, use YAML, TOML, or HCL.
- Is YAML really insecure?
- Default YAML loaders in some languages (notably Python's yaml.load before 2020, Ruby's Psych) can instantiate arbitrary classes from input, which is a remote code execution risk if the input is attacker-controlled. The fix is to use the 'safe' loader (yaml.safe_load in Python, YAML.safe_load in Ruby) which only emits primitive types. Never feed untrusted YAML to a default loader.
- What about TOML or HCL?
- Both are excellent for configuration. TOML is the format Rust's Cargo and Python's pyproject.toml use — predictable, comment-friendly, and harder to misread than YAML. HCL is HashiCorp's format (Terraform). For new projects where you control the tooling, TOML is often the best of the three. JSON and YAML still dominate where the consumer is fixed (Kubernetes manifests are YAML; package.json is JSON; npm and the world won't change for you).
- Why do my big numbers come back wrong from JSON?
- JSON numbers have no precision limit in the spec, but JavaScript (and many JSON parsers) deserialize them into IEEE 754 doubles, which lose precision above 2^53 - 1 (about 9.007 * 10^15). A 19-digit Twitter snowflake ID or a Stripe object ID over that boundary will round. Encode large integers as strings, or use a parser that supports BigInt.
Sources & references
Authoritative references cited by this piece. Verified by Buğra Sözeri on the dates shown and re-checked at every deploy.
- RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format — Current authoritative JSON spec — explicitly excludes comments and trailing commas(as of )
- YAML 1.2.2 specification — The current YAML spec; supersedes 1.1 and fixes the Norway problem(as of )
- ECMA-404 — The JSON Data Interchange Syntax — The other authoritative JSON spec, identical grammar to RFC 8259(as of )
- PyYAML safe_load documentation — Reference for the YAML deserialization-RCE class of bugs(as of )
Related
- JSON ↔ YAML converterPaste either format and convert losslessly to the other
- JSON formatter & validatorPretty-print, minify, and validate JSON with a clear error position
- IEEE 754 glossaryThe floating-point format behind JSON's number-precision trap
- BigInt glossaryHow to represent integers larger than 2^53
Published May 31, 2026