Your config file looks right. The pipeline fails anyway. Somewhere in a YAML file, NO became false, or 1 became an integer when the template engine expected a string. JSON and YAML look similar on the surface but handle data fundamentally differently. Picking the wrong one costs real debugging time.
Same Config, Two Formats
Here's a database configuration in both formats.
{
"host": "localhost",
"port": 5432,
"credentials": {
"user": "deploy",
"password": "s3cret"
},
"ssl": true,
"options": {
"pool_size": 10,
"timeout_seconds": 30
}
}
And the equivalent in YAML:
host: localhost
port: 5432
credentials:
user: deploy
password: s3cret
ssl: true
options:
pool_size: 10
timeout_seconds: 30
The YAML version is cleaner. No braces, no commas, no quotes around keys. That's the appeal. But notice what's invisible in the YAML version: every value's type depends on how the parser interprets it. The JSON version explicitly quotes strings and leaves numbers unquoted. YAML guesses.
JSON: Boring by Design
JSON's strictness gets called annoying until it saves you. The format makes no assumptions. A string is a string because it's quoted. A number is a number because it's not. An object starts with { and ends with }. There's no ambiguity.
This makes JSON the right choice whenever config files cross system boundaries. Your CI pipeline generates a config file? JSON. A deployment tool merges configs from multiple environments? JSON. An orchestrator reads a config at startup? JSON. The simplicity means every language's JSON parser produces the same result.
The tradeoff is real though. JSON is miserable to write by hand past 20 lines. No comments means you can't explain why a value is what it is. Trailing commas throw errors. Multiline strings need \n escapes that make logs unreadable. I've seen teams add a build step just to strip comments from JSON files. At that point you've already lost.
The Norway Problem
The most infamous YAML bug has its own name. In YAML, the string NO is parsed as the boolean false. So is False, FALSE, n, N, off, On, YES, y, and about 30 other values. The YAML spec defines a long list of strings that silently convert to booleans.
A real deployment pipeline failed because a config file contained:
region: NO
That's the country code for Norway. The parser read it as false. The deployment script checked if region == false and skipped the entire Norway region setup. Nobody noticed until users in Norway couldn't access the service.
The YAML spec prioritizes convenience over safety here, and it's by design. The spec says bare words like NO should be treated as booleans because (and I'm paraphrasing) it's what people expect when writing config by hand. The result is that country codes, short status flags, and human-readable values can silently change type.
When YAML's Flexibility Works
For config files that humans write and maintain directly, YAML's features genuinely help. Comments document why a port was chosen or why a timeout is conservative. Anchors and aliases let you define a value once and reuse it:
defaults: &defaults
retries: 3
timeout: 30
development:
<<: *defaults
debug: true
production:
<<: *defaults
retries: 5
Try writing that without duplication in JSON. You'd need a separate tool or a templating layer. YAML handles it natively.
The danger is when those same features cause bugs that are hard to reproduce. The parser that interprets your config locally isn't the same parser your deployment pipeline uses. Different YAML libraries handle edge cases inconsistently. I've seen a config work in PyYAML and fail in go-yaml because of how they handle deep nesting or duplicate keys.
JSON vs YAML: Key Differences
| Feature | JSON | YAML |
|---|---|---|
| Comments | Not supported | # inline |
| Strings | Must be quoted | Quotes optional |
| Type coercion | None (explicit) | Implicit and aggressive |
| Multiline strings | \n escaping only |
` |
| Anchors / aliases | &anchor and *alias |
|
| Trailing commas | Error | Allowed |
| File size | Compact | Usually larger |
| Parser consistency | High across languages | Varies by implementation |
| Security surface | Minimal | Custom types can execute code |
| Machine generation | Excellent | Indentation-sensitive |
The type coercion row matters more than most developers think. A YAML parser can turn "true" into a boolean, "1.0" into a float, and "1" into an integer (or not, depending on whether the value is quoted). This makes YAML dangerous for configs that are validated against a schema. A schema expects a string and gets an integer. The error message points to the validation layer, not to the YAML parsing step, so you debug the wrong thing.
Which One Should You Use?
JSON for anything generated, merged, or validated by machines. YAML for config files that live in a repo and are primarily maintained by humans.
The rule sounds simple but teams violate it constantly. The most common pattern I see: someone picks YAML because it's readable, then adds a JSON schema to validate it, then fights the type coercion issues until they add a pre-processing step to convert everything to strings. That's four steps to achieve what JSON does by default.
A few practical guidelines that have held up across projects:
- If your CI/CD pipeline writes or modifies config files, use JSON. YAML's indentation rules make automated editing error-prone.
- If the config file is checked into the same repository as the code and edited by hand, YAML is worth the risk.
- If you need comments, use YAML, but pin your YAML parser version and test the edge cases.
- If you're building a public API that accepts config, accept JSON. Your users will have fewer parsing bugs.
Our JSON to YAML converter can translate between both formats, but don't rely on it as a permanent bridge. Pick one format per project and convert only during migrations.
Mixing two config formats in one project adds a cognitive cost that's easy to underestimate. Every time a developer reaches for an existing config file to understand the pattern, they check which format it uses first. That tiny overhead adds up across a team over months. I've never seen a project where supporting both formats was worth the confusion it created. Pick one, document the decision, and move on.