Skip to content
Back to Blog
Tutorials

The YAML Norway Problem and JSON-YAML Differences for Engineers

Why YAML reads "no" as false. Real K8s outages from string quoting. JSON vs YAML choices, indent rules & K8s manifest conversions explained.

14 min read

The YAML Norway Problem and JSON ↔ YAML Differences Engineers Should Know

It was a routine Helm deployment. The team had spent two days tuning a values.yaml file for a multi-region rollout. The chart templated a Kubernetes ConfigMap with locale metadata — including the country code for their Norwegian data center. Someone typed country: NO and committed it. The CI pipeline went green. The deployment went out.

Then the alerts came in.

The ConfigMap contained country: false instead of country: "NO". Every downstream service that read the country field got a boolean instead of a string. The string comparison broke. The routing logic fell through to a default. Traffic that should have stayed in Norway ended up processed by the wrong regional endpoint.

The root cause was a single unquoted string in a YAML file. YAML 1.1 — the version that virtually all Kubernetes tooling uses — treats NO as a boolean false. It treats YES, ON, OFF, Y, N, no, yes, on, off, y, n, and a dozen more variants the same way. No warning. No error. Silently wrong.

JSON does not have this problem. {"country": "NO"} is always a string. YAML’s implicit type coercion is both its greatest convenience and its most dangerous footgun.

This guide covers the full picture: why the Norway problem exists, what changed in YAML 1.2 (and why most tooling ignores it), how to write correct quoting strategies, the indentation rules that trip up newcomers, number precision traps, and four real-world conversion scenarios from Kubernetes manifests to Terraform plans. When you need to safely flatten a JSON value into YAML without this trap, our JSON to YAML converter auto-quotes Norway-prone strings automatically.

JSON vs YAML — When to Use Which

Before diving into the Norway problem, it helps to understand what each format is actually optimized for. They are not interchangeable — each has a design center that makes it the better choice in specific contexts.

DimensionJSONYAML
SyntaxStrict — braces, quotes, commas requiredFlexible — indentation-driven, minimal punctuation
Type systemExplicit: string, number, boolean, null, array, objectImplicit — YAML 1.1 infers types from value shape
Human readabilityDeveloper-friendly, machine-verifiableHuman-friendly, easy to hand-edit
Quote requirementStrings always quotedMost scalars can be unquoted (the source of Norway)
CommentsNot supportedSupported with #
Primary useAPIs, data exchange, modern config systemsKubernetes, Docker Compose, Ansible, CI pipelines
Surprising parsesNone — strict parsingYes — Norway, octal, timestamps
Schema enforcementJSON Schema ecosystemYAML Schema (less tooling)

JSON wins when your data crosses system boundaries — REST APIs, message queues, database serialization. Machines parse it, machines generate it, and the strict syntax makes validation straightforward. Use a JSON Formatter to validate structure before sending.

YAML wins when humans are the primary authors. Kubernetes manifests, GitHub Actions workflows, Helm charts, Ansible playbooks — these are files developers read and edit dozens of times. The reduced punctuation and support for comments make them genuinely more maintainable than their JSON equivalents.

The problem arises at the boundary: when a tool generates JSON (like kubectl get deploy -o json or terraform show -json) and a human needs to version-control or edit the result as YAML. That conversion is where the Norway problem lives. Our YAML to JSON converter handles the reverse direction when you need to go back.

The Norway Problem — Deep Dive

The Norway problem is not a bug. It is a feature of the YAML 1.1 specification behaving exactly as designed. Understanding why it was designed this way — and why so many systems still implement 1.1 — is the key to avoiding it.

Why “no”, “yes”, “on”, “off”, “y”, “n” Misparse

The YAML 1.1 specification defined a broad boolean type that was intended to be human-friendly. It recognized all of the following as true or false:

True: y, Y, yes, Yes, YES, true, True, TRUE, on, On, ON

False: n, N, no, No, NO, false, False, FALSE, off, Off, OFF

The intent was good: config files often use yes/no instead of true/false in English, and YAML wanted to support the natural way people write configuration. The problem is that yes, no, on, off, y, and n are also perfectly legitimate string values that mean something entirely different in most applications.

Here is the mismatch in concrete YAML:

# YAML 1.1 (what most parsers implement)
country: NO        # parses as: country: false   ← DANGER
enabled: yes       # parses as: enabled: true
restart: off       # parses as: restart: false
language: y        # parses as: language: true
shell: n           # parses as: shell: false

# Correct — explicit string quotes override type inference
country: "NO"      # parses as: country: "NO"    ← safe
enabled: "yes"     # parses as: enabled: "yes"
restart: "off"     # parses as: restart: "off"
language: "y"      # parses as: language: "y"
shell: "n"         # parses as: shell: "n"

And the JSON comparison:

{"country": "NO"}

In JSON, NO inside quotes is always and unconditionally a string. There is no implicit type inference. The strictness that makes JSON feel verbose is also what makes it safe.

Beyond boolean coercion, YAML 1.1 also implicitly converts:

  • 123e4 → the number 1230000 (scientific notation)
  • 0x1A → the number 26 (hexadecimal)
  • 0755 → the number 493 (octal — this one breaks Unix file permission strings)
  • 2024-05-04 → a date object in many parsers (not just a string)
  • 1_000_000 → the number 1000000 (underscore separator)

The Norway problem is really just the most famous member of a whole family of YAML implicit type coercions.

YAML 1.1 vs 1.2 — What Changed

YAML 1.2 was published in 2009 — four years after YAML 1.1. Its primary goal was to bring YAML into strict alignment with JSON (since JSON is actually a valid YAML 1.2 subset) and to reduce the surprising implicit type conversions.

In YAML 1.2:

  • Boolean is narrowed to exactly true and false (case-sensitive). That is it. yes, no, on, off are plain strings.
  • Octal literals require the 0o prefix (0o755) — the old 0755 form is a string.
  • Timestamps are not implicitly parsed — 2024-05-04 stays a string unless you tag it explicitly.
  • The specification itself is a JSON superset, meaning every valid JSON document is valid YAML 1.2.

On paper, YAML 1.2 solves the Norway problem entirely. In practice, the ecosystem barely moved.

LibraryDefault specNorway risk
PyYAML (Python)YAML 1.1Yes — yaml.safe_load still parses NO as False
ruamel.yaml (Python)YAML 1.2 (optional)Configurable — safer by default
js-yaml (Node.js)YAML 1.1Yes in older versions; newer versions have FAILSAFE_SCHEMA option
eemeli/yaml (Node.js)YAML 1.2No — 1.2 by default, or explicitly version-selectable
gopkg.in/yaml.v2 (Go)YAML 1.1Yes
gopkg.in/yaml.v3 (Go)YAML 1.2Significantly safer
Kubernetes / HelmYAML 1.1 (via Go yaml.v2)Yes — historical, very difficult to migrate
AnsibleYAML 1.1 (via PyYAML)Yes

The reason migration is slow is backward compatibility. Systems that have relied on yes/no parsing as booleans for a decade cannot silently change that behavior without breaking existing configs. Kubernetes in particular is a massive installed base where changing YAML parsing semantics would be a cluster-wide breaking change.

The practical conclusion: assume YAML 1.1 semantics in any tool you did not explicitly configure otherwise. Always quote strings that could be misread as booleans, timestamps, or numbers.

How Production Systems Get Bitten

The Norway country code is the most-cited example because it is counterintuitive — NO looks like an obvious abbreviation, not a boolean. But the pattern repeats across many real-world scenarios:

IATA airport codes. The Norwegian airport Harstad/Narvik has code EVE. Safe. Oslo Gardermoen is OSL. Also safe. But any application using YAML to store regional airport codes is one no route code away from a boolean false in production.

Environment variable names. ON is a perfectly valid environment variable value meaning “enabled” in some legacy systems. OFF is its counterpart. Migrating configs from shell scripts to YAML without quoting these values introduces silent type coercion.

Email user fields. A user whose first name or username is literally n, y, or any of the trigger words will serialize incorrectly if the application dumps YAML without proper quoting. This is particularly insidious because it fails for only a subset of users.

Docker Compose restart policies. The restart_policy field’s value "no" means “do not restart.” If it loses its quotes in a YAML round-trip, the value becomes false, and Docker Compose may interpret it as “no restart policy specified” or throw a validation error — either way, the container restart behavior is wrong.

GitHub Actions shell: field. The valid shell values are bash, pwsh, python, sh, cmd, powershell. None of these are Norway words. But someone who types shell: yes or shell: on as a placeholder during draft editing may be surprised when YAML turns it into a boolean before the validator even sees it.

The fix in all cases is the same: quote strings that are semantically strings, regardless of whether a human would recognize them as keywords. Our JSON to YAML converter applies this automatically — any value in the Norway-word list gets quoted in the output.

String Quoting Strategy

Once you understand why Norway words mismatch, the solution is choosing the right quoting strategy for your use case. YAML supports three modes, each with different tradeoffs.

Auto vs Double vs Single

Auto quoting (recommended for most conversions) lets the library decide when quotes are necessary. Values that would be misread without quotes — Norway words, numbers, timestamps, strings that look like YAML syntax — get quoted automatically. Everything else stays as a plain scalar. This produces the most readable output while remaining safe.

# Auto mode output
name: Alice          # plain — no ambiguity
country: "NO"        # quoted — Norway word
age: 30              # plain — unambiguous number
created: "2024-05-04" # quoted — would otherwise parse as a date
port: "8080"         # depends on library — some quote numeric-looking strings

Double-quoted strings wrap all string values in double quotes. This is explicit and auditable — any reader can see that all these values are strings without reasoning about the spec. The tradeoff is verbosity and reduced human readability, especially for deeply nested configs.

# Double-quote mode
name: "Alice"
country: "NO"
replicas: "3"         # even numbers become strings — may cause schema errors

Be careful: if your target schema expects a number and you serialize it as a quoted string, the YAML parser will correctly type it as a string, but Kubernetes or another strict consumer may reject the field as the wrong type.

Single-quoted strings are a YAML-only feature — JSON has no single-quote syntax. Single quotes are literal: no escape sequences inside them. The only special case is that a single quote inside a single-quoted string must be doubled (''). Single quotes are ideal for strings that contain backslashes or special characters that would need escaping in double quotes.

# Single-quote mode
pattern: 'C:\Users\alice\Documents'  # no escape needed
regex: '\d+\.\d+'                    # backslashes literal

For JSON-to-YAML conversions intended to round-trip back to JSON, prefer Auto or Double mode. Single-quoted strings introduce a YAML-specific syntax that requires a YAML-aware parser on the way back.

Block Scalars (| and >)

YAML’s block scalar syntax is genuinely useful for multi-line strings — something JSON handles awkwardly with \n escape sequences.

Literal block scalar | preserves newlines exactly:

# Literal block — newlines kept
script: |
  #!/bin/bash
  set -euo pipefail
  echo "Starting deployment"
  kubectl apply -f manifest.yaml

# Equivalent JSON representation (unreadable)
# {"script": "#!/bin/bash\nset -euo pipefail\necho \"Starting deployment\"\nkubectl apply -f manifest.yaml\n"}

Folded block scalar > joins lines with spaces, turning each newline into a space (except blank lines, which become newlines):

# Folded block — newlines become spaces
description: >
  This service handles authentication
  for the entire platform. It supports
  OAuth2, SAML, and API key authentication.

# Result: "This service handles authentication for the entire platform. It supports OAuth2, SAML, and API key authentication.\n"

Block scalars shine for embedding TLS certificates, multi-line shell scripts, or SQL queries in YAML configs — scenarios where the JSON equivalent would be a long, escaped, one-liner that no human can read.

When converting from JSON to YAML, most converters (including ours) use Auto mode and represent multi-line strings with block scalars only when they detect embedded newlines. Single-line strings get flow scalars (quoted or plain). Use our JSON to YAML converter to see the output before committing it to a manifest.

Indentation — 2 vs 4 Spaces, Tabs Forbidden

YAML’s indentation rules are stricter than they look. The spec has one absolute rule and one convention that varies by ecosystem.

The absolute rule: tabs are forbidden. Every indentation level must use spaces. A tab character in a YAML file is a parse error in most parsers:

# WRONG — tabs cause parse errors
apiVersion: apps/v1
kind: Deployment
metadata:
	name: my-app     # ← tab character here → ParseError

# CORRECT — spaces only
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app      # ← two spaces

The error message you will see varies by library. In Python’s PyYAML:

yaml.scanner.ScannerError: while scanning for the next token
found character '\t' that cannot start any token

In Go’s yaml.v3:

yaml: line 4: found character that cannot start any token

Configure your editor to expand tabs to spaces for YAML files. In VS Code, add to your workspace settings: "[yaml]": { "editor.insertSpaces": true, "editor.tabSize": 2 }.

The convention: 2 vs 4 spaces. Both are valid. Ecosystem conventions differ:

EcosystemConventionReason
Kubernetes manifests2 spacesOfficial docs and examples use 2
Helm charts2 spacesFollows K8s convention
Docker Compose2 spacesOfficial compose spec examples
GitHub Actions2 spacesOfficial workflow examples
Ansible playbooks2 spacesOfficial documentation
Traditional configs4 spacesMatches JSON beautify default

For any file that will be consumed by Kubernetes or Docker Compose, use 2 spaces. For standalone config files that will only be read by humans and custom tooling, either works — just be consistent within a file. Our JSON to YAML converter defaults to 2-space indentation and lets you switch to 4 for projects that prefer it.

One more rule: child elements must be indented more than their parent, but the number of additional spaces can be any positive integer (1, 2, 3, 4…) — as long as it is consistent within a block. In practice, always use 2 or 4 for readability.

Number Handling Across JSON ↔ YAML

Both formats support numbers, but the edge cases differ enough to cause production bugs.

Precision Loss for Big Numbers

JavaScript’s Number type is a 64-bit IEEE 754 float. It can represent integers exactly up to 2^53 − 1 = 9,007,199,254,740,991. Beyond that, integer precision is lost:

// JavaScript precision loss — this is not a YAML problem, but it affects JSON parsing
JSON.parse('{"v": 9007199254740993}').v
// → 9007199254740992   (the 3 became 2 — one bit lost)

// Safe — within 2^53 range
JSON.parse('{"v": 9007199254740991}').v
// → 9007199254740991   (exact)

This matters for JSON-to-YAML conversion in JavaScript environments because the precision is already lost before YAML serialization begins. Kubernetes metadata.resourceVersion is a string field specifically because resource versions can exceed the safe integer range. Other fields that look like small numbers — observedGeneration, uid components — are safer, but any int64 field in a K8s response is potentially affected.

Workarounds:

  • Use Python or Go for conversion pipelines involving large numbers — both handle arbitrary integers natively.
  • In Node.js, use a JSON parser that supports BigInt: JSON.parse(text, (_, v) => typeof v === 'number' && !Number.isSafeInteger(v) ? BigInt(v) : v).
  • For fields that must round-trip without loss, serialize them as strings at the source.
  • When reviewing converted YAML, look for fields like resourceVersion, generation, and timestamp-derived values.

Octal & Hex Quirks

YAML 1.1 treats certain number-like strings as non-decimal integers:

# YAML 1.1 parsing surprises
permissions: 0755   # parses as octal 493, not decimal 755
value: 0x1A         # parses as hex 26, not string "0x1A"

# YAML 1.2 behavior
permissions: 0755   # stays as integer 755 (decimal) — octal requires 0o prefix
permissions: 0o755  # parses as octal 493 in both 1.1 and 1.2

# Safe for both specs — quote any leading-zero value
permissions: "0755"  # always the string "0755"

The octal trap is particularly dangerous for Unix file permissions, IP address components with leading zeros (some network devices), and any numeric code that uses leading zeros for padding (ZIP codes, product codes). Always quote these values when writing YAML by hand, or ensure your converter quotes them — our JSON to YAML converter detects numeric strings from JSON and preserves their string type.

Real-World Conversions

The Norway problem and quoting strategies become concrete when you apply them to real conversion scenarios.

Kubernetes Manifest from JSON

The canonical workflow: kubectl get deploy my-app -o json gives you the live object as JSON. You want to clean it up (remove status, creationTimestamp, managed fields) and check it into git as a YAML manifest.

Source JSON (abbreviated):

{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "my-app",
    "namespace": "production",
    "labels": {
      "app": "my-app",
      "region": "NO"
    }
  },
  "spec": {
    "replicas": 3,
    "selector": {
      "matchLabels": { "app": "my-app" }
    },
    "template": {
      "spec": {
        "containers": [{
          "name": "app",
          "image": "registry.example.com/my-app:v1.2.3",
          "env": [
            { "name": "REGION", "value": "NO" },
            { "name": "ENABLE_FEATURE", "value": "yes" }
          ]
        }]
      }
    }
  }
}

Expected YAML output (with Norway protection):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
  labels:
    app: my-app
    region: "NO"          # quoted — Norway word
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    spec:
      containers:
        - name: app
          image: registry.example.com/my-app:v1.2.3
          env:
            - name: REGION
              value: "NO"           # quoted — Norway word
            - name: ENABLE_FEATURE
              value: "yes"          # quoted — Norway word

Notice that replicas: 3 is left unquoted — it is a legitimate integer that Kubernetes expects as a number. The Norway words in labels and env values are quoted. A naive converter that does not handle YAML 1.1 booleans would silently produce region: false and value: false.

After converting, validate with: kubectl apply --dry-run=client -f manifest.yaml. This catches schema errors without touching the cluster.

Try the conversion in our JSON to YAML converter — paste the JSON above and see Norway-safe output instantly. Use our YAML to JSON converter to verify the round-trip.

Docker Compose from JSON

CI/CD pipelines sometimes generate Docker Compose configs programmatically from a JSON configuration store, then write them to disk as YAML for developers to read.

Critical trap — restart policy:

{"restart_policy": "no"}

In Compose, restart_policy: "no" is a valid value meaning “never restart the container.” Without quotes in YAML, this becomes restart_policy: false, which Docker Compose may either treat as the same semantic (falsy = no restart) or reject with a type validation error — behavior varies by Compose version. The quoting is mandatory.

Also watch for: Compose v3 deploy.restart_policy.condition: "on-failure" — the on-failure value contains the word on, but it is hyphenated and not in the trigger list, so it is actually safe. However, condition: on (without the -failure) would mismatch. Quote environment variable values in the environment: block if they could be Norway words.

Validate Compose files after conversion: docker-compose config parses and re-outputs the canonical form, surfacing type errors.

GitHub Actions Workflow

GitHub Actions workflows are YAML files hand-edited by developers. The most common conversion scenario is reading workflow data from the GitHub API (which returns JSON) and converting it to a local YAML file for editing.

The key fields to watch:

# SAFE — no Norway words in standard GitHub Actions
on:                        # "on" is a YAML key here, not a value — handled differently
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: |
          npm install
          npm test
        env:
          NODE_ENV: production   # safe — not a Norway word
          DEBUG: "off"           # Norway word in value — needs quoting

Note: on: as a YAML key is special — the Norway problem applies to values, not keys. But on as a value (like DEBUG: on) would trigger the coercion. The env: block deserves particular scrutiny because environment variable values are strings, but many of them are short flags that could collide with Norway words.

For workflows that include shell: specifications, valid values (bash, pwsh, sh, python) are all safe from Norway coercion. Custom values should be quoted proactively.

Terraform JSON Plan → YAML

terraform show -json tfplan > plan.json outputs a detailed JSON representation of what Terraform plans to create, modify, or destroy. Converting this to YAML makes it more readable for pull request reviews and compliance audits.

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# Then convert with our tool or a library

The Terraform plan JSON is complex and deep. Key concerns when converting:

  1. Large integer IDs. Cloud resource IDs (AWS account IDs, GCP project numbers) and computed attribute values can be large numbers. Convert via Python or Go to avoid float64 precision loss.

  2. Version constraint strings. Terraform uses ~>, >=, <= in provider version constraints. These are string values that YAML handles correctly as long as they are not Norway words — but ~> is safe.

  3. Provider configuration values. Terraform plan outputs can include configuration values for resources. If a boolean field defaults to false and is represented as "no" in some provider schema, that is a Norway risk on the way back to YAML.

  4. The .sensitive_values block. Sensitive values are redacted as true booleans in the plan JSON. These survive conversion cleanly since true is not a Norway word in either YAML version.

The Terraform-to-YAML conversion is primarily for human review, not for feeding back into Terraform. Do not use YAML manifests as Terraform input — Terraform’s native format is HCL, and its JSON input format is specific and documented separately.

Code Examples — 4 Languages

Node.js (eemeli/yaml + js-yaml)

The Node.js ecosystem has two dominant YAML libraries with meaningfully different Norway handling:

// eemeli/yaml — recommended, YAML 1.2 by default, Norway-safe
import { stringify } from 'yaml';
import { readFileSync } from 'fs';

const jsonInput = readFileSync('input.json', 'utf8');
const data = JSON.parse(jsonInput);

// Default: YAML 1.2 — "NO" stays as "NO", no boolean coercion
const yamlOutput = stringify(data);
console.log(yamlOutput);
// region: NO      ← safe in 1.2, but for maximum compatibility quote it explicitly

// Force YAML 1.1 behavior (for K8s/Helm environments that parse 1.1)
const yamlForK8s = stringify(data, { version: '1.1' });
// region: 'NO'    ← auto-quoted because 1.1 would parse NO as false
console.log(yamlForK8s);
// js-yaml — widespread, but YAML 1.1 semantics, Norway-risky without care
import yaml from 'js-yaml';
import { readFileSync } from 'fs';

const data = JSON.parse(readFileSync('input.json', 'utf8'));

// Default dump — Norway words may not be quoted
const unsafe = yaml.dump(data);
// region: NO    ← will parse as false if re-read by a 1.1 parser!

// Safer: use a custom schema or force quoting
const safer = yaml.dump(data, {
  schema: yaml.JSON_SCHEMA,   // restricts to JSON-compatible types
  noCompatMode: false,
  lineWidth: -1,
  quotingType: '"',
  forceQuotes: false,         // only quotes when necessary per JSON schema
});

For new projects, prefer eemeli/yaml. Its YAML 1.2 default is safer, its Document API gives fine-grained control over quoting, and it handles the round-trip fidelity better. For projects already using js-yaml, use the JSON_SCHEMA option to restrict to JSON-safe types. For a deeper look at filtering and transforming JSON before conversion, see the jq command-line cheat sheet for pre-processing patterns.

Python (PyYAML + ruamel.yaml)

Python is the dominant language for Kubernetes tooling, Ansible, and data engineering pipelines — all heavy YAML users.

import json
import yaml
import sys

# PyYAML — simple, standard, but YAML 1.1 by default
with open('input.json') as f:
    data = json.load(f)

output = yaml.dump(data, default_flow_style=False, allow_unicode=True)
# country: 'NO'   ← PyYAML is actually smart enough to auto-quote Norway words
# But it does NOT quote "yes", "no" (lowercase) in all configurations:
# enabled: 'yes'   ← quoted
# tag: y           ← may or may not be quoted depending on version

print(output)
import json
import sys
from ruamel.yaml import YAML

# ruamel.yaml — round-trip fidelity, supports YAML 1.2, recommended for production
yaml_rt = YAML()
yaml_rt.default_flow_style = False
yaml_rt.width = 4096              # prevent unwanted line wrapping
yaml_rt.best_map_flow_style = False

with open('input.json') as f:
    data = json.load(f)

yaml_rt.dump(data, sys.stdout)
# Preserves key order, handles Norway words correctly, supports anchors on round-trip

For Ansible and Kubernetes automation scripts where you are converting JSON API responses to YAML manifests, ruamel.yaml is the safer choice. PyYAML is fine for simple scripts where you control the input data and have verified no Norway words appear.

If you use JSON5 or JSONC config files (with comments) before conversion, strip the extensions first — see the JSON5 and JSONC formatting guide for compatible parsers.

Go (gopkg.in/yaml.v3)

Go is the language of the Kubernetes ecosystem itself — kubectl, Helm, Argo, Flux, and most K8s operators are written in Go.

package main

import (
    "encoding/json"
    "fmt"
    "os"

    "gopkg.in/yaml.v3"
)

func main() {
    // Read JSON input
    jsonBytes, err := os.ReadFile("input.json")
    if err != nil {
        panic(err)
    }

    // Unmarshal JSON into a generic map
    var data map[string]interface{}
    if err := json.Unmarshal(jsonBytes, &data); err != nil {
        panic(err)
    }

    // Marshal to YAML — yaml.v3 uses YAML 1.2 semantics
    yamlBytes, err := yaml.Marshal(data)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(yamlBytes))
    // country: "NO"     ← yaml.v3 quotes Norway words correctly
    // replicas: 3       ← integers stay integers
    // enabled: true     ← booleans stay booleans
}

yaml.v3 is a significant improvement over yaml.v2 for Norway safety. The v2 library followed YAML 1.1 and would write NO without quotes; v3 quotes ambiguous values correctly. If you are maintaining an older Go project that uses v2, upgrade to v3 — the API is largely compatible and the safety improvement is worth the migration.

For type-safe conversion with Go structs (rather than map[string]interface{}), use struct tags:

type DeploymentLabels struct {
    App    string `yaml:"app" json:"app"`
    Region string `yaml:"region" json:"region"`
}
// yaml.Marshal on a struct field containing "NO" will quote it correctly in v3

Bash CLI (yq + jq)

For shell scripts and quick one-off conversions, yq (Mike Farah’s version, mikefarah/yq) converts JSON to YAML in a single command:

# Install yq
brew install yq                         # macOS
sudo wget -qO /usr/local/bin/yq \
  https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
chmod +x /usr/local/bin/yq              # Linux

# Convert JSON file to YAML
yq -P < input.json > output.yaml

# Convert from kubectl JSON output
kubectl get deploy my-app -o json | yq -P > manifest.yaml

# Pipe through jq first to filter/transform, then convert to YAML
kubectl get deploy my-app -o json \
  | jq 'del(.status, .metadata.creationTimestamp, .metadata.managedFields)' \
  | yq -P > clean-manifest.yaml

The jq | yq pipeline is a powerful pattern: use jq for JSON manipulation (filtering fields, reshaping structure, querying values) and yq -P as the final YAML serializer. For jq patterns, see the jq command-line cheat sheet for 30 real-world patterns including kubectl and aws integrations.

Norway caution with yq: yq (mikefarah) respects the input type from JSON — a JSON string "NO" in the input will serialize as a YAML string with quotes. But if you generate YAML directly with yq (not from JSON input), you must quote Norway-word values explicitly. Use our YAML to JSON converter to validate the round-trip after yq output.

Edge Cases & Gotchas

Beyond the Norway problem, JSON ↔ YAML conversion has several edge cases that trip up experienced engineers:

  1. Multi-document YAML (--- separator). A single YAML file can contain multiple documents separated by ---. JSON has no equivalent concept. When converting multi-document YAML to JSON, most tools either take the first document only, merge all documents into an array, or error out. When converting JSON to YAML, a single --- document header is added by convention. Decide and document your behavior explicitly for pipelines that may encounter multi-document files.

  2. YAML anchors and aliases. YAML supports &anchor definitions and *alias references for DRY configs. When converting YAML to JSON, anchors must be expanded — the resulting JSON may be much larger than the source YAML. When converting JSON to YAML, the converter cannot reconstruct anchors that did not exist in the original. Aliases are a YAML-only feature.

  3. Timestamp implicit parsing. YAML 1.1 parsers convert 2024-05-04 and 2024-05-04T12:00:00Z to language-native date objects, not strings. When this date object is serialized back to JSON, the output depends on the library: some output ISO strings, some output Unix timestamps, some output null. Round-tripping dates through YAML without explicit string quoting ("2024-05-04") can silently change the format.

  4. The !!binary tag. YAML can embed base64-encoded binary data with the !!binary tag. JSON has no binary type — binary must be a base64 string. When converting YAML with !!binary fields to JSON, decode to base64 string. When converting back, you cannot reconstruct the binary tag without knowing the schema. Kubernetes uses !!binary for some secret values.

  5. Key type collisions. JSON requires object keys to be strings. YAML allows keys of any type — integer keys, boolean keys, even complex object keys. A YAML file with true: value or 1: value as keys cannot be faithfully represented as JSON. Most converters stringify the keys, but the semantics change.

  6. Null representation variance. In YAML, null, ~, Null, NULL, and an empty value all mean null. In JSON, only null is null. When converting YAML to JSON, all of these normalize to null. But when converting JSON back to YAML, the null representation choice matters — ~ is more compact, null is more explicit. Pick one and stick to it.

  7. Sort order changes. JSON objects technically have no defined key order (though most parsers preserve insertion order). YAML mappings similarly have no required order. But some YAML libraries sort keys alphabetically by default when serializing. This can cause large diffs in version control if the source JSON used a different order. Configure sort_keys=False in PyYAML (default_flow_style=False alone does not prevent sorting) and equivalent options in other libraries.

When NOT to Convert

Conversion is not always the right answer. Here are the scenarios where staying in the original format is the better choice:

Do not convert YAML to JSON if the YAML contains comments that document business logic. YAML comments are not part of the data model — they disappear in any serialization to JSON. If a Kubernetes manifest has comments explaining why a specific resource limit was chosen or why a security policy exception was made, converting to JSON destroys that documentation. Keep the YAML.

Do not auto-convert configs in CI pipelines without round-trip tests. If your pipeline converts JSON to YAML and then applies the YAML to a cluster, add a round-trip validation step: YAML back to JSON, then compare with the original. This catches type coercion surprises before they reach production.

Do not convert just because a tool outputs JSON. kubectl, aws, terraform, and docker inspect all output JSON, but most of these tools also accept YAML as input. Before building a conversion step, check whether the target tool can directly accept YAML input — most modern DevOps tools can. Our YAML to JSON converter is most useful when you specifically need JSON for a tool that does not accept YAML.

Do not convert if the schemas differ. If your JSON uses camelCase keys and your YAML consumer expects snake_case (or vice versa), you need a transform step in addition to a format conversion. A bare format conversion will produce syntactically correct but semantically wrong YAML. Address the schema mapping explicitly.

Do not keep both formats in sync manually. If you are maintaining a config.json and a config.yaml that are supposed to be equivalent, you will drift. Pick one canonical format and derive the other automatically — or better, pick one format and eliminate the duplication.

FAQ

Does the YAML Norway problem still affect modern systems?

Yes — it is pervasive in the ecosystem. Kubernetes and Helm use Go’s yaml.v2 library (YAML 1.1 semantics) in significant parts of their codebases. Ansible uses PyYAML (YAML 1.1). GitHub Actions workflows are parsed by GitHub’s internal YAML parser which has its own behavior. Most CI/CD YAML files in the wild are processed by YAML 1.1 parsers. Assume 1.1 semantics until you have verified otherwise.

Why would I convert JSON to YAML if YAML is harder to parse?

The conversion is not about parser difficulty — it is about human editability. JSON is ideal for machines; YAML is ideal for humans who need to read, edit, and review configuration files. A Kubernetes manifest checked into git, reviewed in pull requests, and hand-tuned by engineers should be YAML. The same manifest retrieved from the API for programmatic processing should be JSON. Our JSON to YAML converter bridges the two.

Can I round-trip JSON ↔ YAML losslessly?

With caveats, yes — for JSON-compatible data. JSON is a subset of YAML 1.2, so any valid JSON document is valid YAML 1.2. Going JSON → YAML → JSON should be lossless for any data without implicit type coercion. The Norway problem means a JSON string "NO" could survive the forward pass only if the converter quotes it, and then survive the return pass only if the YAML parser respects the quotes. Use a YAML 1.2 library for both directions to guarantee lossless round-trips.

What is the safest YAML library for production?

For Python: ruamel.yaml configured for YAML 1.2. For Node.js: eemeli/yaml (the yaml package on npm). For Go: gopkg.in/yaml.v3. All three implement YAML 1.2 semantics or have explicit YAML 1.2 modes and handle Norway words correctly. Avoid YAML 1.1 libraries in new projects. If you must use a 1.1 library (PyYAML, js-yaml, yaml.v2) for compatibility reasons, always quote Norway-prone strings explicitly.

Does Kubernetes manifest YAML support comments after JSON conversion?

No — comments cannot be recovered from JSON. JSON has no comment syntax, so there is nothing to convert. When you run kubectl get deploy -o json and convert the output to YAML for git storage, the resulting YAML will have no comments. Comments in a Kubernetes manifest must be written by a human after the conversion. This is one reason why keeping the hand-authored YAML as the canonical source is often preferable to round-tripping through the JSON API.

How do I handle big integers like resourceVersion or nanosecond timestamps?

Kubernetes metadata.resourceVersion is a string field deliberately — the Kubernetes team knew that JSON parsers in JavaScript and other float64-based runtimes would lose precision on large integers. Always treat it as a string. For genuinely numeric large integers (like nanosecond epoch timestamps in some tracing systems), use Python’s int type, Go’s int64, or Node.js BigInt for parsing. Never pass them through JSON.parse() in JavaScript without a custom reviver function. When converting to YAML, these large integers are safe — YAML has no precision limit for integers. The danger is in the round-trip back through JavaScript’s JSON parser.

Is YAML 1.2 widely adopted yet?

Unevenly. The major language libraries have been migrating: Go’s yaml.v3, Python’s ruamel.yaml, and Node.js’s eemeli/yaml all support or default to YAML 1.2. But Kubernetes, Ansible, and much of the DevOps ecosystem still runs on YAML 1.1 parsers due to the backward-compatibility cost of migration. YAML 1.2 adoption in new projects is recommended, but assume 1.1 for any system you did not configure yourself.

Should our team standardize on JSON or YAML for configs?

Standardize on purpose, not on format. Use JSON for configs consumed by code (API request bodies, SDK config files, programmatic tooling). Use YAML for configs consumed by humans (Kubernetes manifests, CI pipelines, deployment configs, Ansible playbooks). Avoid mixing the two for the same config — pick one representation per config type and automate the conversion if you need both. When you do need to convert, both our JSON to YAML and YAML to JSON converters run entirely in your browser — no data leaves your device.

Try It Now

Ready to convert a real file? Try our JSON to YAML converter for sanitizing JSON into safe Kubernetes YAML — it auto-quotes Norway words (NO, yes, on, off, and the full YAML 1.1 boolean list) and lets you choose 2-space or 4-space indentation. For the reverse direction, our YAML to JSON converter handles anchors, aliases, and multi-document YAML. Both tools run entirely in your browser — your data never leaves your device, which matters when you are working with production Kubernetes manifests or Terraform plans that contain sensitive resource configurations.

Related Articles

View all articles