BytePane

JSON to YAML Converter: Convert Between Formats Instantly

Data Formats15 min read

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" not version: 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:

JSON
{
  "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"
              }
            ]
          }
        ]
      }
    }
  }
}
YAML (with comment)
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: production

The 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

FeatureJSONYAMLImpact on Conversion
Comments✗ Not supported✓ # comment syntaxLost on YAML→JSON
Anchors & aliases✗ Not supported✓ &anchor / *refExpanded on YAML→JSON
Multi-line stringsEscaped \n in strings| (literal) > (folded)Converted to string with \n
Data types6 types, no coercionAuto-coerces unquoted values (1.1)Quote ambiguous YAML values
Structure syntaxBraces, brackets, commasIndentation onlyConvert with parser, never regex
Multiple documents✗ One document only✓ --- separatorSplit into separate JSONs
Custom types✗ Not supported✓ !!python/object tagsLost/error on YAML→JSON
Binary data✗ Use Base64 string✓ !!binary typeMust be encoded pre-convert
Key orderingUnspecified (de facto preserved)UnspecifiedNo guarantee of order preservation
File size (typical)Larger (key repetition)~40% smaller for same dataStructural difference, not convertible

JSON to YAML Conversion: Production-Ready Code

JavaScript / Node.js: js-yaml

js-yaml — JSON ↔ YAML bidirectional conversion
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: 3

Python: PyYAML vs ruamel.yaml

Python — JSON to YAML with both PyYAML and 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.yaml

Go: 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
Detecting coercion before YAML→JSON conversion (Python)
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
ToolDirectionYAML VersionPreserves CommentsBest For
yq (Go)BothYAML 1.2Round-trip: yesCLI scripting, pipelines
js-yamlBothYAML 1.2No (dump)Node.js apps, browser
PyYAMLBothYAML 1.1NoPython scripts (wide compat)
ruamel.yamlBothYAML 1.2Yes (round-trip mode)Python, editing existing YAML
gopkg.in/yaml.v3BothYAML 1.2NoGo applications
BytePane JSON→YAMLJSON→YAMLYAML 1.2N/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:

JSON
REST API requests and responses
Universal browser support, native JavaScript, fast to parse, every HTTP library speaks JSON. YAML adds a parsing dependency with no benefit.
YAML
Kubernetes manifests, Helm charts
kubectl and Helm read YAML natively. The ecosystem is YAML-first. Converting to JSON adds friction with no benefit.
YAML
GitHub Actions, GitLab CI workflows
Platform requires YAML. Comments in workflow files are valuable for documentation.
YAML
Docker Compose files
docker compose uses YAML. Anchors are useful for shared service configuration.
TOML or YAML
Application config loaded at startup
TOML (pyproject.toml, Cargo.toml) is unambiguous and readable. YAML if your platform standardizes it. Avoid JSON for human-edited config — comments matter.
JSON
Data interchange between services
JSON is the lingua franca of web services. Libraries exist in every language. No implicit coercions.
JSON
Config files under code review (diffs)
JSON diffs are clearer than YAML: added/removed braces and commas are unambiguous. YAML indentation changes produce confusing diffs.

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.