If you’ve ever opened a Kubernetes manifest, a GitHub Actions workflow, or a docker-compose file, you’ve used YAML. If you’ve ever called a REST API or written a config file for a Node.js project, you’ve used JSON. They’re both serialization formats. They both describe the same kinds of data structures — maps, arrays, strings, numbers, booleans, null. So why do we have two of them?
The short answer: YAML optimises for humans, JSON optimises for machines. The long answer involves a few decades of developer experience, a few committee meetings, and a surprising amount of edge cases. Let’s walk through it.
What JSON is good at
JSON (JavaScript Object Notation) was popularised in the early 2000s as a payload format for AJAX requests. It was deliberately small — six value types, no comments, no trailing commas, no ambiguity. That strictness is its superpower on the wire:
- Parsers are tiny and fast.
JSON.parseships with every browser and every language runtime. There’s nothing to think about. - Errors are unambiguous. When JSON is broken, the parser tells you exactly which line and column.
- Machines agree on semantics. A number is a number, a string is a string,
trueistrue. There is no “isyesa boolean or a string?” debate.
That’s why JSON is the lingua franca of APIs. When two services are talking, you want the format with the fewest surprises.
What YAML is good at
YAML (YAML Ain’t Markup Language) was designed for configuration files written by humans. It traded some of JSON’s strictness for readability:
- No braces, no brackets. Structure is indentation. A
docker-compose.ymlreads top-to-bottom like a list. - Comments. A
#starts a comment. JSON has no comment syntax — and the lack of comments is the single most common reason teams move from JSON to YAML for config files. - Anchors and references.
&anchorand*referencelet you define a value once and reuse it. JSON has no equivalent; you have to copy-paste. Learn more in our YAML anchors explained guide. - Multi-line strings. Literal blocks (
|) and folded blocks (>) handle prose and code without escape-juggling.
That’s why YAML dominates in places humans read and write by hand: Kubernetes, GitHub Actions, GitLab CI, Ansible playbooks, docker-compose, CloudFormation, OpenAPI specs, and the 2025-era surge of AI-agent config files (.github/agents.yml, MCP server manifests, evaluation harnesses).
The asymmetry
Here’s the dirty secret: YAML is a superset of JSON’s data model. Every valid JSON file can be expressed as YAML, but the reverse isn’t true. YAML has features JSON simply doesn’t have — comments, anchors, multi-doc files, custom tags. Once you write YAML with anchors, you can’t losslessly convert it back to JSON. The anchors disappear.
This matters for tooling. If you have a YAML file with &defaults *defaults, converting to JSON will either expand the references (silently producing a larger file) or drop them (silently producing a smaller file). The same goes for comments. JSON has nowhere to put them.
When to use which
Use JSON when:
- You’re sending data over the network (APIs, message queues, event payloads).
- The file is read by code, not humans.
- You want unambiguous error messages from strict parsers.
- You’re optimising for parse speed on constrained devices.
Use YAML when:
- A human is the primary author and reader of the file.
- You want comments for explaining intent.
- You want to reuse chunks of config with anchors.
- The tooling in your domain expects it (Kubernetes, CI, etc.).
The pragmatic answer for most teams: JSON for the wire, YAML for config, both for data interchange. When in doubt, store the canonical version in JSON (lossless) and emit YAML for human consumption (with comments and anchors) only when the audience is a developer opening a text editor.
Common pitfalls when converting between the two
- Numbers and booleans are auto-typed in YAML.
port: 8080is a number.port: "8080"is a string. Forgetting to quote can change your type. - Anchors and comments are dropped in JSON. No way around this — the format has no place to put them.
- YAML
nullis ambiguous.key:(empty value) isnull.key: nullis alsonull.key: ""is an empty string. Three different things. - Tabs break YAML. Indentation must be spaces. Tabs are an error.
- YAML 1.1 vs 1.2 vs 1.2.2. YAML 1.1 treated
yes,no,on,offas booleans. YAML 1.2 (the spec used by js-yaml, PyYAML, and most parsers in 2026) does not. The 1.2.2 maintenance release in 2025 tightened a few edge cases around document-end markers and BOM handling — most parsers adopted the fixes silently. If you’re moving from a 2018-era codebase, your config might still break in subtle ways.
Tools that help
When you’re moving between the two — and you will, because every team uses both — a fast client-side converter is your friend. The YAML to JSON and JSON to YAML tools on DevSpeedTools run entirely in your browser, so config files with secrets never leave your machine. Open DevTools → Network and you’ll see no requests carrying your data.
For YAML that’s grown messy over time, the YAML formatter will normalise indentation to two spaces and fix inconsistent quoting. For files that won’t parse, the YAML validator shows you the exact line and column of every error.
The bottom line: stop treating YAML and JSON as competing formats. They’re complementary. The right format depends on who’s reading the file — a machine, or a tired developer at 11pm trying to figure out why their deployment is broken.