At some point you'll write a GitHub Actions workflow file, squint at the indentation, and think "why isn't this just JSON?" Ten minutes later you'll be editing a tsconfig.json that doesn't support comments, and think the opposite. Both formats exist for good reasons, and picking the wrong one for the job genuinely makes your life harder.
Let me walk through where each one shines — and where it bites.
The same data, two very different vibes
Here's a simple configuration block in both formats:
YAML:
server:
host: localhost
port: 8080
debug: true
allowed_origins:
- https://example.com
- https://staging.example.com
JSON:
{
"server": {
"host": "localhost",
"port": 8080,
"debug": true,
"allowed_origins": [
"https://example.com",
"https://staging.example.com"
]
}
}
Same information, very different feel. The YAML version looks like a note someone wrote; the JSON version looks like a data structure someone serialized. That difference in feel maps directly to their actual use cases.
Where they really differ
Comments — YAML wins, clearly
This is the biggest practical difference for config files. When you're writing a docker-compose.yml and you want to explain why a specific port is mapped, or leave a note about a deprecated env variable, YAML lets you do that:
services:
web:
# Port 3001 because 3000 conflicts with the local dev server
ports:
- "3001:3000"
environment:
# Deprecated — will be removed in v2. Use DATABASE_URL instead.
DB_HOST: localhost
JSON has no equivalent. None. The workarounds ("_comment" keys, // keys) are hacks and they pollute your data. If the file is something a human edits regularly, the inability to annotate it is a real cost.
YAML's type flexibility is a double-edged sword
YAML automatically infers types, which is mostly helpful and occasionally catastrophic. Here's a classic one that's bitten a lot of DevOps engineers:
# In older YAML (1.1), these are all booleans: true
enabled: yes
flag: on
active: true
# These are all booleans: false
disabled: no
toggle: off
inactive: false
So if you have a country code NO (Norway) in a YAML file and the parser treats it as false, your deployment config is now broken in a way that's surprisingly hard to debug. YAML 1.2 fixed this, but a lot of parsers still use 1.1 behavior. JSON would never have this problem — "NO" is just the string "NO", end of story.
The flip side is that YAML handles things JSON genuinely can't express cleanly. Multiline strings are a good example:
error_message: |
Something went wrong during deployment.
Please check the logs at /var/log/app.log
and contact support if the issue persists.
In JSON that becomes "error_message": "Something went wrong during deployment.\nPlease check the logs at /var/log/app.log\nand contact support if the issue persists." — which is technically valid but genuinely unpleasant to read or edit.
Indentation sensitivity in YAML
JSON uses braces and brackets, so structure is explicit. YAML uses whitespace, which means a single extra space or a tab-vs-space mixup can completely change the meaning of a file — or break it outright. If you've ever spent 20 minutes debugging a Kubernetes manifest only to find you had four spaces where you needed two, you know the feeling.
# This is a list under "items"
items:
- alpha
- beta
# This is TWO separate top-level keys (probably not what you wanted)
items:
- alpha
- beta
Most editors handle this for you now, but it's still a footgun that JSON avoids entirely.
The practical breakdown
Reach for JSON when:
- You're building or consuming a REST API.
Content-Type: application/jsonis the lingua franca of the web. Don't fight it. - You need something that works in a browser without extra dependencies.
JSON.parse()is everywhere. - You want parsing to be fast and unambiguous. Configuration-as-data that gets read frequently and never hand-edited belongs in JSON.
- You're storing data in a database or log file. Strict structure catches bugs early.
Reach for YAML when:
- A human is going to read and edit this file regularly. CI/CD pipelines, Docker Compose, Kubernetes manifests — these all use YAML because operators need to understand them at a glance.
- You need inline documentation. Comments in config files aren't optional when your infrastructure grows complex.
- You're using a framework that expects it. GitHub Actions, Ansible, and Helm charts are all YAML-first. Don't fight the ecosystem.
The honest answer
For most greenfield work, the choice is already made for you by the framework. GitHub Actions is YAML. REST APIs are JSON. package.json is JSON. Kubernetes is YAML. Follow the convention of your ecosystem and save your energy for things that actually matter.
When you do have a choice — say, an internal configuration format — I'd default to JSON for anything machine-generated or frequently parsed, and YAML for anything operator-edited with documentation needs.
Converting between them
If you're stuck with data in one format and need the other — maybe migrating a config from one tool to another, or you need to validate some YAML you got from an external source — the YAML ↔ JSON Converter handles the conversion instantly in both directions without any setup.