TabOnly

JSON to YAML

Convert JSON to YAML with output that still means the same thing when Python reads it.

input
output
output appears here

JSON that still means the same thing after a YAML reader touches it

Converting JSON to YAML looks like a two-function problem: parse the JSON, stringify the value. The part that actually matters is which YAML comes out, because the default output of every JavaScript YAML writer is read incorrectly by the most widely deployed YAML reader in the world. YAML 1.2 treats no, on, and 12:34:56 as plain strings. PyYAML, Ruby's Psych, and older Go libraries still implement YAML 1.1, whose core schema resolves those exact spellings as booleans or a sexagesimal number. Measured this session against PyYAML 6.0.3: of twelve representative string values, six changed type when read back under 1.1. This page defaults to the portable setting that survives both, and says so with a real table instead of a silent choice.

Paste an object, array, or bare scalar — all three are valid JSON documents, and a lone 42 or null is not an error here. Output uses block style with two-space indentation, matching what a Kubernetes manifest or GitHub Actions workflow already looks like, and multi-line strings become | block scalars instead of a quoted string full of \n. Line folding is off: this output is meant for a repository, and a value that wraps mid-diff is worse than a long line. Where the portable toggle is switched off, the page lists which values would be misread under YAML 1.1 rather than just quietly changing what it emits.

Some things are simply gone before this page ever runs. JSON's own parser has already reduced duplicate keys to the last value and rounded any integer past 253; this page cannot recover either, and the FAQ below explains why rather than pretending otherwise. YAML comments, anchors, and multiple documents are not invented on the way out, because the JSON input never contained them — those are properties of a YAML source, which is what yaml-to-json converts from.

Do it without this tool

Same conversion, your own toolchain. The Python one is the snippet to actually copy — default_flow_style=False is easy to leave out and changes the whole output shape.

python · block style, not flow style
import json, yaml

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

print(yaml.safe_dump(data, default_flow_style=False, sort_keys=False))

# default_flow_style=None (PyYAML's own default) mixes in {a: 1}-style
# flow mappings for anything it thinks is "short". Force block style
# if you want output that reads like a hand-written config.
shell · yq (the Go one, mikefarah/yq)
yq -P data.json > data.yaml

# -P (--prettyPrint) is required — without it yq emits YAML flow style,
# which is valid but is not the block style most configs expect.
# python-yq (kislyuk/yq, a jq wrapper) uses a different flag: yq -y
node · the yaml package, portable mode
import YAML from 'yaml';

YAML.stringify(data, { version: '1.1', lineWidth: 0 });
// version: '1.1' is what makes the writer quote "no", "yes", "12:34:56" —
// it quotes the superset both 1.1 and 1.2 readers agree needs quoting.
// lineWidth defaults to 80 and will fold long values across lines; 0 disables it.
go · yaml.v2 and yaml.v3 disagree on this exact line
import "gopkg.in/yaml.v3" // resolves bare "no"/"on" as YAML 1.2 strings
import "gopkg.in/yaml.v2" // resolves the same tokens as YAML 1.1 booleans

out, _ := yaml.Marshal(data)
// Swapping v2 for v3 in an existing Go codebase can silently change what
// a config file means, with no error and no diff in the Go source.

How the two YAML versions disagree

YAML 1.2.2 §10.2 defines the core schema this page targets by default: a closed set of literal tags for null, booleans, and numbers, under which no and on are ordinary strings. YAML 1.1 §10.3 defines a different, larger resolution table — the one PyYAML, Psych, and gopkg.in/yaml.v2 implement — where those same spellings resolve to booleans, and where a bare HH:MM:SS token resolves as a sexagesimal integer. RFC 8259 defines the JSON this page reads, and it has no equivalent ambiguity: JSON has exactly one boolean spelling and one number grammar, which is part of why this problem is invisible until the JSON leaves JSON and becomes YAML. Portable mode, measured against PyYAML 6.0.3, quotes the union of tokens both versions treat specially, which is a strict superset of what YAML 1.2 alone would require quoted.

FAQ

Why does the output put quotes around no?

YAML 1.2, the schema this page writes under by default, treats bare no as the string "no". YAML 1.1 — what PyYAML, Ruby's Psych, and older Go yaml.v2 libraries implement — resolves the identical bareword as the boolean false. This is the “Norway problem”: ISO 3166's country code for Norway is NO, and a long history of YAML configs that meant a country code wrote a boolean instead. The ambiguous set is closed — no/yes/on/off/y/n and case variants, plus numbers that only look plain such as 12:34:56 and 0755 — and this page's portable toggle, on by default, quotes every string in that set so a YAML 1.1 or 1.2 reader agrees on what it got. Turn portable off and the output matches what the yaml library emits unquoted, which is correct only if every consumer reads YAML 1.2.

Why is 12:34:56 a number in Python and a string in JavaScript?

YAML 1.1's core schema resolves a bare HH:MM:SS token as sexagesimal — base 60 — so PyYAML reads 12:34:56 as the integer 45296 (12×3600 + 34×60 + 56). It is a holdover from the earliest Perl YAML implementations and has no equivalent rule in YAML 1.2, which the yaml library used here defaults to and which reads the same text as a plain string. Same bytes, two different values, and the difference lives entirely in which spec version the reader implements — which is why the portable toggle exists: with it on, this value goes out quoted, and no reader can reinterpret it as a number regardless of which YAML version it runs.

Can JSON comments survive the conversion?

No, because there is nothing to carry. JSON's grammar (RFC 8259) has no comment production, so whatever you paste has already lost any comments that existed before it became JSON text — this page cannot recover what was never in its input. Going the other direction is a different, real loss: a YAML file's comments do exist in that source, and yaml-to-json has to drop them on the way to JSON, which is why that page's own FAQ covers it separately rather than repeating this answer.

Why doesn't a long URL or string ever wrap onto multiple lines?

It's deliberate, not a bug. The yaml library's own default line width is 80 columns, and past that it folds a plain scalar across several lines — semantically lossless, since a YAML reader rejoins folded lines on a space, but visually disruptive. This page's output is meant to be committed to a repository, and a folded value turns a one-line diff of a changed URL into a multi-line diff that's harder to review and easy to corrupt by hand-editing a fold point. So line width is fixed at 0 here — never fold — which is not what the library's own defaults would do; every value stays on one line no matter how long.

What happens to an integer past 2^53?

It is already lost before this page runs. JSON.parse('{"n":9007199254740993}') returns {n: 9007199254740992} in every JavaScript engine, because IEEE 754 doubles cannot represent that integer exactly — the rounding happens in the platform's own JSON parser, not in this converter's code. What you pasted stays visible on screen, so you can read the true digits back off your own input, but the value handed to the YAML writer is already the rounded one. If that number came from an API, the fix belongs at the source — serialize the field as a string — because nothing downstream of JSON.parse can recover the missing precision.

related tools