Converting Between JSON and YAML: What Survives and What Doesn't
YAML is a superset of JSON, so every JSON document is valid YAML and converting JSON → YAML is lossless. The other direction is different: YAML has features with no JSON equivalent, and they quietly disappear or change shape in translation. Whichever way you're converting, that asymmetry is the thing to know.
The same data, two notations
YAML is pleasant to hand-write because quotes, braces, and commas are mostly unnecessary and comments are allowed. That's why human-edited configuration — Kubernetes manifests, GitHub Actions workflows, Docker Compose files — is YAML, while data exchanged between programs is JSON.
{
"name": "my-app",
"version": "1.0.0",
"ports": [8080, 8443],
"env": {
"NODE_ENV": "production",
"DEBUG": false
}
}name: my-app version: 1.0.0 ports: - 8080 - 8443 env: NODE_ENV: production DEBUG: false
How to use it
- Paste the JSON or YAML you want to convert.
- Choose the conversion direction.
- Review the result; invalid input syntax produces an error message.
- When converting YAML → JSON, check the loss table below first.
What YAML loses on the way to JSON
Comments hurt the most in practice. Comments in a Kubernetes manifest or CI config often hold knowledge that isn't expressible in the config itself — why a value must not change, for instance — and a round trip through JSON erases all of it. Don't round-trip a config file and overwrite the original.
| YAML feature | After conversion to JSON |
|---|---|
| # comments | Gone entirely |
| Anchors (&) and aliases (*) | Expanded — values duplicated inline |
| Multiple documents (--- separated) | Only the first survives, or an error |
| Block scalars (| and >) | A single string containing \n |
| Date types | Converted to strings |
| Non-string keys (numbers, booleans) | Coerced to string keys |
| Tags (!!str and friends) | Lost |
Anchors and aliases
YAML has syntax for reusing values: define with &name, reference with *name, and merge mappings with <<: *name. Docker Compose and GitLab CI configs use it constantly to declare shared settings once. Converting to JSON expands every reference into the actual value, so the result is correct but duplicated and larger.
defaults: &defaults adapter: postgres encoding: utf8 development: <<: *defaults database: myapp_dev test: <<: *defaults database: myapp_test
How YAML's type inference causes outages
YAML infers the type of unquoted values, and that convenience is behind some famous bugs. The best known is the Norway problem: the country code NO, written unquoted, is parsed as the boolean false by YAML 1.1 parsers. For the same reason yes, no, on, off, y, and n all become booleans.
Version numbers are another frequent casualty. version: 1.0 parses as the number 1.0 rather than the string "1.0", while version: 1.0.0 has two dots and stays a string — so the same file becomes inconsistent. Leading-zero numbers like 08 are treated as octal and either error or change value, and time-like notation (12:30) is read as base-60 and becomes 750 in some implementations.
The fix is simple: quote anything that must be a string. Make it a habit for versions, country and language codes, phone numbers, identifiers with leading zeros, and time-like values.
country: NO # → false (a boolean!) country: "NO" # → "NO" version: 1.0 # → 1 (a number) version: "1.0" # → "1.0" zip: 08301 # → parsed as octal; error or wrong value zip: "08301" # → "08301" enabled: yes # → true enabled: "yes" # → "yes"
Indentation rules
- Tabs are forbidden. The YAML spec prohibits them outright, so use spaces — an editor configured to insert tabs produces parse errors that are hard to trace.
- The indent width is up to you but must be consistent within a level. Two spaces is conventional.
- A list item's hyphen may sit in the parent's column or be indented one level. Both are valid; pick one per file.
- A space is required after the colon. key:value parses as a single string.
- Values containing a colon followed by a space must be quoted.
Which format to choose
| Situation | Use | Why |
|---|---|---|
| API requests and responses | JSON | Parsers everywhere, no ambiguity |
| Human-edited configuration | YAML | Comments and terse syntax |
| Logs (one record per line) | JSON Lines | Suits line-based processing and streaming |
| Build and CI pipeline definitions | YAML | Ecosystem standard |
| Externally published schemas | JSON | Mature validation tooling |
| Parsing untrusted input | JSON | YAML parsers do far more, so the risk surface is larger |
Frequently Asked Questions
- Is every JSON document valid YAML?
- Per the YAML 1.2 spec, yes — 1.2 is defined as a JSON superset, so you can feed JSON straight to a YAML parser. Many implementations still follow YAML 1.1, where a few exceptions exist, so verify with your actual parser if it's on a critical path.
- Is key order preserved?
- This tool preserves input order. Note though that YAML mappings are specified as unordered, so passing the data through other tools may reorder them. It's best not to build designs that depend on key order.
- Is there any way to keep YAML comments?
- Not through JSON, which has no comment syntax. If you need to modify YAML programmatically while keeping comments, use a round-trip parser that preserves them — ruamel.yaml in Python, or a YAML AST library elsewhere. The key is not using JSON as an intermediate step.
- What's the difference between | and > for multi-line strings?
- | is a literal block that keeps newlines as-is; > is a folded block that turns newlines into spaces. Use | for embedded scripts and > for long prose wrapped across lines. Adding - (|-) strips the final newline, and + keeps trailing blank lines.
- Can parsing YAML be a security risk?
- Yes. Some languages' default YAML loaders can instantiate arbitrary objects via tags, so parsing untrusted input can lead to code execution. Use yaml.safe_load in Python and YAML.safe_load in Ruby. JavaScript's js-yaml defaults to a safe schema, but check the options you pass to load.
- How do I convert multiple --- separated documents?
- A JSON document has exactly one top-level value, so several YAML documents can't map directly onto one JSON document. Convert each separately, or wrap them all in a JSON array. This comes up constantly with Kubernetes manifests.
💡 Note: If a YAML value arrives as the wrong type, it's almost always type inference. Check whether values that should be strings are quoted.