JSON & YAML
Volume 3 — Modern Development
You'll write both of these constantly without necessarily having learned either formally — JSON in every API payload and appsettings.json, YAML in every Azure DevOps pipeline. This chapter covers the syntax rules and, more usefully, the specific gotchas that cause real bugs in each.
1. JSON Syntax
{
"id": 42,
"employeeName": "Pete Carroll",
"status": "Pending",
"isUrgent": false,
"requestedDate": "2026-08-01",
"approver": null,
"tags": ["vacation", "planned"],
"metadata": {
"createdBy": "system",
"revisionCount": 2
}
}
| Rule | Detail |
|---|---|
| Keys | Always double-quoted strings — 'id' and unquoted id are both invalid |
| Strings | Double quotes only, no single quotes |
| Numbers | No quotes; no leading zeros, no trailing decimal point without a digit after it |
| Booleans | Lowercase true/false, unquoted |
| Null | Lowercase null, unquoted |
| Trailing commas | Not allowed — a comma after the last item/property is a syntax error |
| Comments | Not supported at all — JSON has no comment syntax, in any form |
The trailing-comma and no-comments rules are the two that trip people up most, especially coming from JavaScript, where both are tolerated in object/array literals but not in strict JSON.
2. YAML Syntax
service: roundtrip-api
environment: production
replicas: 3
autoScale: true
tags:
- api
- production
database:
host: db.internal
port: 1433
ssl: true
| Rule | Detail |
|---|---|
| Indentation | Significant — defines nesting, like Python. Spaces only, never tabs. |
| Key-value | key: value, a space required after the colon |
| Lists | Either - item on its own indented lines, or inline [item1, item2] |
| Strings | Usually don't need quotes, but quote anything containing :, #, or that could be misread as another type |
| Comments | # comment — supported, unlike JSON |
3. YAML's Sharp Edges
These aren't stylistic quibbles — each one has caused real production and pipeline bugs across the industry, repeatedly, for exactly the same reasons.
Tabs are a hard error, not a style preference
service: api
replicas: 3 # tab character used for indentation — this is a parse error, not just "not preferred"
Most editors auto-convert tabs to spaces for YAML files specifically because of this — but a copy-paste from a tab-indented source can silently reintroduce the problem.
The "Norway problem" — unquoted no/yes/on/off
country: NO # parsed as the boolean `false`, not the string "NO" — this is the actual, real "Norway problem"
autoDeploy: yes # parsed as boolean `true`
YAML 1.1 (still what many parsers implement) treats yes, no, on, off, true, false — in various cases — as booleans even when unquoted and clearly intended as strings. Always quote values like country codes or anything that happens to match a boolean keyword:
country: "NO"
autoDeploy: "yes" # or restructure to avoid the ambiguity entirely
Numbers that look like they should be strings
version: 1.0 # parsed as a float, not the string "1.0" — can silently become 1 in some contexts
zipCode: 06701 # leading zero — may parse unexpectedly depending on the parser
Quote anything where the string representation itself matters, not just its numeric value.
Indentation-based nesting mistakes
database:
host: db.internal
port: 1433 # one extra space — this is now nested UNDER host, not a sibling of it
Unlike a missing brace in JSON (which usually fails loudly), a YAML indentation mistake often still parses successfully — just into a different structure than intended, silently. This is the most common source of "the pipeline ran, but the step I added never actually executed" bugs.
4. JSON vs. YAML: When to Use Which
| JSON | YAML | |
|---|---|---|
| Typical use | API request/response payloads, appsettings.json | Configuration files, CI/CD pipelines, Docker Compose |
| Human-editability | More verbose, more punctuation | More readable for deeply nested config, supports comments |
| Comments | Not supported | Supported |
| Ambiguity risk | Low — strict, unambiguous grammar | Higher — the Norway problem, tabs, indentation |
| Machine generation | The near-universal default for anything generated/consumed programmatically | Rare to generate programmatically; almost always hand-written |
The rough rule: if a machine writes it and another machine reads it, JSON. If a human writes and maintains it directly, YAML's comments and lower punctuation overhead usually win — which is exactly why API payloads are JSON and Azure DevOps pipelines are YAML.
5. Where These Show Up Across Traxs
| File | Format | Notes |
|---|---|---|
appsettings.json / appsettings.Development.json | JSON | .NET configuration, environment-specific overrides |
| API request/response bodies | JSON | Every RoundTrip/Waypoint endpoint |
azure-pipelines.yml / azure-pipelines-dev.yml | YAML | CI/CD pipeline definitions — indentation mistakes here are exactly what caused the dev/prod branch-mixing incident (TRA-195), a --branch flag on the wrong indentation level in the wrong pipeline stage |
docker-compose.yml | YAML | Local multi-container orchestration (see the Docker Compose chapter) |
6. Validating Before You Commit
JSON:
cat appsettings.json | python3 -m json.tool > /dev/null && echo "valid" || echo "invalid"
YAML (via a quick Python one-liner, since a dedicated linter isn't always installed):
python3 -c "import yaml, sys; yaml.safe_load(open('azure-pipelines.yml'))" && echo "valid" || echo "invalid"
For pipeline YAML specifically, Azure DevOps also offers a "validate" option on the pipeline before running it — worth using after any indentation-sensitive edit, since a successful parse doesn't guarantee correct structure, only that it's not malformed.
7. Troubleshooting Playbook
| Symptom | Likely cause | What to check |
|---|---|---|
| JSON parse error, "unexpected token" | Trailing comma, single quotes, or a stray comment | Run it through a formatter/linter; both trailing commas and comments are invalid JSON regardless of source |
| A YAML boolean-looking value behaves unexpectedly | Norway problem — unquoted yes/no/on/off coerced to boolean | Quote the value explicitly |
| Pipeline step silently doesn't run | Indentation nested it under the wrong parent, parsed successfully into the wrong structure | Diff against a known-good version of the file; check indentation level against the intended parent key precisely |
"expected <block end>, but found..." YAML error | Tab character mixed into indentation | Search the file for tab characters explicitly; most editors can highlight/convert them |
Config value is 1 instead of the expected "1.0" | Unquoted value parsed as a number, losing the string format | Quote it explicitly if the string representation matters |
| appsettings value not overriding as expected | JSON merge behavior in .NET config layering not doing what was assumed (arrays don't merge, they replace) | Confirm exactly which config file/layer is providing the active value at runtime |
8. Quick Reference
| Category | Rule | Format |
|---|---|---|
| JSON | Keys must be double-quoted | Both |
| JSON | No trailing commas | JSON only |
| JSON | No comments supported | JSON only |
| YAML | Spaces only, never tabs | YAML only |
| YAML | Quote yes/no/on/off/country codes | YAML only |
| YAML | Comments via # | YAML only |
| Validate JSON | python3 -m json.tool | — |
| Validate YAML | python3 -c "import yaml; yaml.safe_load(open('f.yml'))" | — |
| Use JSON for | Machine-generated/consumed payloads | API bodies, appsettings.json |
| Use YAML for | Human-maintained configuration | Pipelines, Docker Compose |
Part of the Traxs Engineering Handbook — Volume 3: Modern Development. Companion chapters in this volume: Docker Fundamentals, Docker Compose, REST APIs, Authentication & Authorization, Postman Guide.