JSON to YAML Converter: Convert Between Formats Instantly
Debunking "YAML Is Just Human-Readable JSON"
A common mental model among developers: YAML is JSON with nicer syntax. You drop the braces and quotes, add indentation, and that's it. This model is wrong enough to cause real bugs. YAML is technically a superset of JSON — every valid JSON document is valid YAML 1.2 — but the relationship breaks down in practice because:
- Most tools still use YAML 1.1 (PyYAML, older Ruby gems, many CI parsers), not YAML 1.2. YAML 1.1 applies implicit type coercions that silently change the semantics of values that are not quoted.
- YAML has features JSON fundamentally lacks: comments, anchors, aliases, multi-line strings, and custom type tags. Converting from YAML to JSON loses all of these.
- YAML's indentation-as-structure means a copy-paste error changes your data hierarchy without a parser error. JSON's explicit delimiters make structural mistakes immediately visible.
According to developer surveys compiled by devtoolbox.dedyn.io, the most popular formats in the 2026 developer ecosystem are: JSON for APIs (universally supported, fast to parse, native to JavaScript), YAML for configuration (Kubernetes, Docker Compose, GitHub Actions, Ansible, Helm), and TOML for project configuration (Rust's Cargo.toml, Python's pyproject.toml). Each format exists because it serves a different use case — converting between them requires understanding what is lost or changed in translation.
Key Takeaways
- ▸JSON-to-YAML conversion is lossless for basic types. YAML-to-JSON conversion loses comments, anchors, and multi-document structure — those YAML features have no JSON equivalent.
- ▸YAML 1.1's implicit coercions (
YES → true,0755 → 493,1.0 → float 1.0) are the most common source of unexpected behavior. Quote ambiguous values in YAML. - ▸The recommended 2026 stack: APIs use JSON, CI/CD uses YAML, app configs use TOML. Convert only at format boundaries, not throughout your codebase.
- ▸Use ruamel.yaml (Python) instead of PyYAML when you need YAML 1.2 semantics or need to preserve comments and formatting during round-trip edits.
- ▸In YAML config files, always quote version strings and country/environment names:
version: "1.10"notversion: 1.10(which parses as float 1.1).
JSON vs YAML: Side-by-Side Syntax Comparison
Before converting between formats, it helps to see them expressing the same data. Here is a Kubernetes-style deployment configuration in both formats:
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "web-app",
"labels": {
"app": "web-app",
"version": "1.10"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "web-app"
}
},
"template": {
"spec": {
"containers": [
{
"name": "web",
"image": "myapp:latest",
"ports": [
{ "containerPort": 8080 }
],
"env": [
{
"name": "NODE_ENV",
"value": "production"
}
]
}
]
}
}
}
}apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
version: "1.10" # quoted: prevents float coercion
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
spec:
containers:
- name: web
image: myapp:latest
ports:
- containerPort: 8080
env:
- name: NODE_ENV
value: productionThe YAML version is ~40% fewer characters, significantly easier to read, and supports the comment explaining why version is quoted. The JSON version is unambiguous, portable to any tool, and suitable for API responses.
JSON vs YAML Feature Comparison
| Feature | JSON | YAML | Impact on Conversion |
|---|---|---|---|
| Comments | ✗ Not supported | ✓ # comment syntax | Lost on YAML→JSON |
| Anchors & aliases | ✗ Not supported | ✓ &anchor / *ref | Expanded on YAML→JSON |
| Multi-line strings | Escaped \n in strings | | (literal) > (folded) | Converted to string with \n |
| Data types | 6 types, no coercion | Auto-coerces unquoted values (1.1) | Quote ambiguous YAML values |
| Structure syntax | Braces, brackets, commas | Indentation only | Convert with parser, never regex |
| Multiple documents | ✗ One document only | ✓ --- separator | Split into separate JSONs |
| Custom types | ✗ Not supported | ✓ !!python/object tags | Lost/error on YAML→JSON |
| Binary data | ✗ Use Base64 string | ✓ !!binary type | Must be encoded pre-convert |
| Key ordering | Unspecified (de facto preserved) | Unspecified | No guarantee of order preservation |
| File size (typical) | Larger (key repetition) | ~40% smaller for same data | Structural difference, not convertible |
JSON to YAML Conversion: Production-Ready Code
JavaScript / Node.js: js-yaml
import yaml from 'js-yaml'
// ── JSON to YAML ──────────────────────────────────────────────
function jsonToYaml(jsonString: string, indent = 2): string {
// Parse JSON first to validate syntax
const data = JSON.parse(jsonString)
return yaml.dump(data, {
indent, // indentation (default: 2 spaces)
lineWidth: 120, // wrap long strings at 120 chars (-1 = never wrap)
noRefs: true, // disable anchor generation (keep output simple)
sortKeys: false, // preserve key order from JSON
quotingType: '"', // use double quotes for strings that need quoting
})
}
// ── YAML to JSON ──────────────────────────────────────────────
function yamlToJson(yamlString: string, pretty = true): string {
// yaml.load() parses the first YAML document
const data = yaml.load(yamlString)
return JSON.stringify(data, null, pretty ? 2 : 0)
}
// ── Handle multi-document YAML ────────────────────────────────
function yamlMultiDocToJsonArray(yamlString: string): string {
// yaml.loadAll() handles --- separated documents
const documents: unknown[] = []
yaml.loadAll(yamlString, (doc) => { documents.push(doc) })
return JSON.stringify(documents, null, 2)
}
// ── Example ───────────────────────────────────────────────────
const json = JSON.stringify({
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: { name: 'web-app', labels: { version: '1.10' } },
spec: { replicas: 3 },
}, null, 2)
console.log(jsonToYaml(json))
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: web-app
// labels:
// version: '1.10'
// spec:
// replicas: 3Python: PyYAML vs ruamel.yaml
import json
import yaml
from io import StringIO
# ── PyYAML (most common, YAML 1.1 semantics) ──────────────────
def json_to_yaml_pyyaml(json_string: str) -> str:
data = json.loads(json_string)
return yaml.dump(
data,
default_flow_style=False, # block style (NOT inline)
allow_unicode=True, # don't escape Unicode as \uXXXX
sort_keys=False, # preserve JSON key order
indent=2,
)
def yaml_to_json_pyyaml(yaml_string: str, pretty: bool = True) -> str:
data = yaml.safe_load(yaml_string) # safe_load, never yaml.load(untrusted)
return json.dumps(data, indent=2 if pretty else None, ensure_ascii=False)
# ── ruamel.yaml (YAML 1.2, preserves comments on round-trip) ──
from ruamel.yaml import YAML
def json_to_yaml_ruamel(json_string: str) -> str:
"""
Use ruamel.yaml when you need YAML 1.2 semantics:
- NO implicit boolean coercion (yes/no stay as strings)
- Preserves comments when round-tripping existing YAML files
"""
data = json.loads(json_string)
ryaml = YAML()
ryaml.default_flow_style = False
ryaml.indent(mapping=2, sequence=4, offset=2)
stream = StringIO()
ryaml.dump(data, stream)
return stream.getvalue()
# ── Multi-document YAML → JSON array ─────────────────────────
def yaml_multidoc_to_json(yaml_string: str) -> str:
documents = list(yaml.safe_load_all(yaml_string))
return json.dumps(documents, indent=2, ensure_ascii=False)
# ── CLI usage with Python stdlib ──────────────────────────────
# python3 -c "
# import json, yaml, sys
# print(yaml.dump(json.load(sys.stdin), default_flow_style=False))
# " < input.json > output.yamlGo: gopkg.in/yaml.v3
package main
import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
)
// JSONToYAML converts JSON bytes to YAML bytes
func JSONToYAML(jsonBytes []byte) ([]byte, error) {
// Parse JSON into generic interface{}
var data interface{}
if err := json.Unmarshal(jsonBytes, &data); err != nil {
return nil, fmt.Errorf("JSON parse error: %w", err)
}
// Convert numbers: json.Unmarshal uses float64 for all numbers
// yaml.Marshal preserves them correctly
return yaml.Marshal(data)
}
// YAMLToJSON converts YAML bytes to JSON bytes
func YAMLToJSON(yamlBytes []byte) ([]byte, error) {
var data interface{}
if err := yaml.Unmarshal(yamlBytes, &data); err != nil {
return nil, fmt.Errorf("YAML parse error: %w", err)
}
// yaml.v3 uses map[string]interface{} for objects and []interface{} for arrays
// — compatible with encoding/json
return json.MarshalIndent(data, "", " ")
}
func main() {
jsonInput := `{"name": "web-app", "replicas": 3, "tags": ["app", "web"]}`
yamlOutput, err := JSONToYAML([]byte(jsonInput))
if err != nil {
panic(err)
}
fmt.Printf("%s", yamlOutput)
// name: web-app
// replicas: 3
// tags:
// - app
// - web
}The YAML Type Coercion Traps (YAML 1.1 vs 1.2)
The most dangerous YAML-to-JSON conversion bugs come from YAML 1.1 implicit type coercions that happen during YAML parsing — before your converter ever sees the value. By the time the data reaches JSON serialization, the type has already changed silently:
# YAML 1.1 coercions that break YAML → JSON conversion # ── Boolean coercions (the "Norway Problem") ────────────────── country: NO # → false (YAML 1.1 booleans: y/n/yes/no/true/false/on/off) country: "NO" # → "NO" (safe: quoted) env: ON # → true active: Yes # → true enabled: FALSE # → false # ── Octal numbers ───────────────────────────────────────────── mode: 0755 # → 493 (octal in YAML 1.1) mode: "0755" # → "0755" (safe: quoted) # ── Floats from version strings ─────────────────────────────── version: 1.10 # → 1.1 (float — trailing zero dropped!) version: "1.10" # → "1.10" (safe: quoted) api_version: 1.0 # → 1.0 (float, displays as 1.0 in JSON but lost precision) # ── Infinity and NaN (not valid JSON) ───────────────────────── score: .inf # → Infinity → JSON serialization fails: not a valid JSON number value: .nan # → NaN → JSON serialization fails # ── Null coercions ──────────────────────────────────────────── key: # → null (empty value) key: ~ # → null key: null # → null (both YAML 1.1 and 1.2) # ── YAML 1.2 (ruamel.yaml, YAML 1.2 parsers) ───────────────── # YAML 1.2 removes all the above ambiguities: # - Only true/false are booleans (case-sensitive, lowercase only) # - No octal 0755 coercion (0o755 is octal in YAML 1.2) # - NO/YES/ON/OFF are plain strings # - Floats must be explicit: 1.0 is float, 1 is integer
import yaml
import json
def yaml_to_json_safe(yaml_string: str) -> str:
"""
Convert YAML to JSON with coercion detection.
Warns when YAML 1.1 coercions change the semantic meaning.
"""
data = yaml.safe_load(yaml_string)
# Detect Infinity/NaN before JSON serialization
def check_json_safe(obj, path=""):
import math
if isinstance(obj, float):
if math.isinf(obj) or math.isnan(obj):
raise ValueError(
f"Cannot convert {path}: {obj} is not valid JSON. "
f"Quote it in YAML: {path.split('.')[-1]}: ".inf""
)
elif isinstance(obj, dict):
for k, v in obj.items():
check_json_safe(v, f"{path}.{k}")
elif isinstance(obj, list):
for i, v in enumerate(obj):
check_json_safe(v, f"{path}[{i}]")
check_json_safe(data)
return json.dumps(data, indent=2, ensure_ascii=False)YAML Anchors: What Gets Lost in JSON Conversion
YAML anchors and aliases allow you to define a value once (&anchor) and reuse it (*alias). This is powerful for avoiding duplication in configuration files. When converting to JSON, anchors are expanded — the JSON output repeats the full value at every alias location. The result is semantically correct but loses the shared-reference structure:
# YAML with anchors and merge key (<<)
defaults: &defaults
image: node:20-alpine
restart: unless-stopped
env:
NODE_ENV: production
services:
api:
<<: *defaults # merge all keys from defaults
ports: ["3000:3000"]
command: ["node", "api/server.js"]
worker:
<<: *defaults # reuse same defaults
ports: []
command: ["node", "worker/index.js"]
# ── After conversion to JSON (anchors expanded) ─────────────────
{
"defaults": {
"image": "node:20-alpine",
"restart": "unless-stopped",
"env": { "NODE_ENV": "production" }
},
"services": {
"api": {
"image": "node:20-alpine", // repeated
"restart": "unless-stopped", // repeated
"env": { "NODE_ENV": "production" }, // repeated
"ports": ["3000:3000"],
"command": ["node", "api/server.js"]
},
"worker": {
"image": "node:20-alpine", // repeated again
"restart": "unless-stopped",
"env": { "NODE_ENV": "production" },
"ports": [],
"command": ["node", "worker/index.js"]
}
}
}CLI Tools for JSON/YAML Conversion
# ── yq (most popular YAML CLI tool) ─────────────────────────
# yq: handles YAML, JSON, TOML, XML. 7,000+ GitHub stars.
# brew install yq / snap install yq / go install github.com/mikefarah/yq/v4@latest
# JSON to YAML
yq -P input.json # -P = prettyprint as YAML
yq eval . input.json -o yaml > output.yaml
# YAML to JSON
yq eval . input.yaml -o json > output.json
# Pretty-print JSON as YAML
echo '{"name":"app","replicas":3}' | yq -P
# ── Python one-liners (no additional packages for basic cases) ─
# JSON → YAML
python3 -c "import sys,json,yaml; print(yaml.dump(json.load(sys.stdin), default_flow_style=False))" < in.json
# YAML → JSON
python3 -c "import sys,json,yaml; print(json.dumps(yaml.safe_load(sys.stdin), indent=2))" < in.yaml
# ── jq + yq pipeline ─────────────────────────────────────────
# Transform JSON and convert to YAML in one pipeline
jq '.spec.template.spec.containers[] | {name, image}' deploy.json | yq -P
# ── Node.js one-liner ─────────────────────────────────────────
# node -e "const yaml=require('js-yaml'),fs=require('fs');
# console.log(yaml.dump(JSON.parse(fs.readFileSync('/dev/stdin','utf8'))))" < in.json| Tool | Direction | YAML Version | Preserves Comments | Best For |
|---|---|---|---|---|
| yq (Go) | Both | YAML 1.2 | Round-trip: yes | CLI scripting, pipelines |
| js-yaml | Both | YAML 1.2 | No (dump) | Node.js apps, browser |
| PyYAML | Both | YAML 1.1 | No | Python scripts (wide compat) |
| ruamel.yaml | Both | YAML 1.2 | Yes (round-trip mode) | Python, editing existing YAML |
| gopkg.in/yaml.v3 | Both | YAML 1.2 | No | Go applications |
| BytePane JSON→YAML | JSON→YAML | YAML 1.2 | N/A (from JSON) | Browser, one-click convert |
Decision Framework: When to Use JSON vs YAML
The format choice is usually dictated by the tool or ecosystem, not by preference. But when you have a choice:
Validating Your Converted Output
After converting, validate the output to confirm the conversion produced the correct structure. For JSON output, use the BytePane JSON formatter to verify syntax and pretty-print. For YAML output, the YAML validator guide covers yamllint and kubeconform for Kubernetes manifests. The YAML vs JSON comparison goes deeper on the semantic differences that affect conversion.
For JSON validation after conversion — especially when converting YAML config files to JSON for API consumption — see the guide on how to validate JSON with AJV and JSON Schema to ensure the converted data matches your expected schema.
Frequently Asked Questions
Is YAML a superset of JSON?
Mostly yes — YAML 1.2 was designed so every valid JSON document is valid YAML. But YAML 1.1 (used by PyYAML, many Ruby gems) applies implicit coercions that change JSON string values: YES/NO become booleans, 0755 becomes the integer 493, 1.10 becomes float 1.1. In practice, you must quote ambiguous YAML values to guarantee round-trip fidelity.
How do I convert JSON to YAML in Python?
Two steps: parse JSON with json.loads(), serialize with yaml.dump(). Full example: import json, yaml; data = json.loads(json_string); result = yaml.dump(data, default_flow_style=False, allow_unicode=True). For YAML 1.2 semantics, use ruamel.yaml instead of PyYAML — it avoids YAML 1.1 coercions and can preserve comments on round-trips.
How do I convert JSON to YAML in JavaScript?
Use js-yaml: npm install js-yaml. Then: import yaml from "js-yaml"; const data = JSON.parse(jsonString); const yamlString = yaml.dump(data). Set noRefs: true to prevent automatic anchor generation. js-yaml defaults to YAML 1.2-compatible output with no implicit type coercions.
What are the main differences between JSON and YAML?
Five key differences: (1) YAML supports comments, JSON does not; (2) YAML uses indentation for structure, JSON uses explicit braces; (3) YAML has multiple string styles including bare/block literals, JSON requires double quotes; (4) YAML supports anchors/aliases for value reuse, JSON has no reference system; (5) YAML 1.1 applies implicit type coercions, JSON has no coercions.
When should I use JSON vs YAML?
Use JSON for APIs, web responses, and data interchange — universally supported, unambiguous, fast. Use YAML for configuration files (Kubernetes, Docker Compose, GitHub Actions) where human readability and comments matter. If the tool or platform dictates the format (like kubectl requiring YAML), use that. When in doubt for human-edited config, YAML wins for readability; for programmatically generated config, JSON wins for clarity.
Why does YAML to JSON conversion produce unexpected types?
YAML 1.1 implicit coercions happen during YAML parsing — before your converter sees the value. YES, NO, ON, OFF become booleans. 0755 becomes 493 (octal). 1.10 becomes float 1.1. The JSON output reflects what the YAML parser produced, not the original text. Prevention: quote ambiguous values in YAML ("YES", "0755", "1.10") so they stay as strings.
Convert JSON to YAML Instantly
Paste your JSON and get clean, properly formatted YAML output. Handles nested objects, arrays, and all JSON types. Runs entirely in your browser.