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.
| Dimension | JSON | YAML |
|---|---|---|
| Syntax | Strict — braces, quotes, commas required | Flexible — indentation-driven, minimal punctuation |
| Type system | Explicit: string, number, boolean, null, array, object | Implicit — YAML 1.1 infers types from value shape |
| Human readability | Developer-friendly, machine-verifiable | Human-friendly, easy to hand-edit |
| Quote requirement | Strings always quoted | Most scalars can be unquoted (the source of Norway) |
| Comments | Not supported | Supported with # |
| Primary use | APIs, data exchange, modern config systems | Kubernetes, Docker Compose, Ansible, CI pipelines |
| Surprising parses | None — strict parsing | Yes — Norway, octal, timestamps |
| Schema enforcement | JSON Schema ecosystem | YAML 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 number1230000(scientific notation)0x1A→ the number26(hexadecimal)0755→ the number493(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 number1000000(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
trueandfalse(case-sensitive). That is it.yes,no,on,offare plain strings. - Octal literals require the
0oprefix (0o755) — the old0755form is a string. - Timestamps are not implicitly parsed —
2024-05-04stays 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.
| Library | Default spec | Norway risk |
|---|---|---|
| PyYAML (Python) | YAML 1.1 | Yes — 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.1 | Yes in older versions; newer versions have FAILSAFE_SCHEMA option |
| eemeli/yaml (Node.js) | YAML 1.2 | No — 1.2 by default, or explicitly version-selectable |
| gopkg.in/yaml.v2 (Go) | YAML 1.1 | Yes |
| gopkg.in/yaml.v3 (Go) | YAML 1.2 | Significantly safer |
| Kubernetes / Helm | YAML 1.1 (via Go yaml.v2) | Yes — historical, very difficult to migrate |
| Ansible | YAML 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:
| Ecosystem | Convention | Reason |
|---|---|---|
| Kubernetes manifests | 2 spaces | Official docs and examples use 2 |
| Helm charts | 2 spaces | Follows K8s convention |
| Docker Compose | 2 spaces | Official compose spec examples |
| GitHub Actions | 2 spaces | Official workflow examples |
| Ansible playbooks | 2 spaces | Official documentation |
| Traditional configs | 4 spaces | Matches 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:
-
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.
-
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. -
Provider configuration values. Terraform plan outputs can include configuration values for resources. If a boolean field defaults to
falseand is represented as"no"in some provider schema, that is a Norway risk on the way back to YAML. -
The
.sensitive_valuesblock. Sensitive values are redacted astruebooleans in the plan JSON. These survive conversion cleanly sincetrueis 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:
-
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. -
YAML anchors and aliases. YAML supports
&anchordefinitions and*aliasreferences 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. -
Timestamp implicit parsing. YAML 1.1 parsers convert
2024-05-04and2024-05-04T12:00:00Zto 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. -
The
!!binarytag. YAML can embed base64-encoded binary data with the!!binarytag. JSON has no binary type — binary must be a base64 string. When converting YAML with!!binaryfields to JSON, decode to base64 string. When converting back, you cannot reconstruct the binary tag without knowing the schema. Kubernetes uses!!binaryfor some secret values. -
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: valueor1: valueas keys cannot be faithfully represented as JSON. Most converters stringify the keys, but the semantics change. -
Null representation variance. In YAML,
null,~,Null,NULL, and an empty value all mean null. In JSON, onlynullis null. When converting YAML to JSON, all of these normalize tonull. But when converting JSON back to YAML, the null representation choice matters —~is more compact,nullis more explicit. Pick one and stick to it. -
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=Falsein PyYAML (default_flow_style=Falsealone 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.