JSON to YAML
Convert JSON to YAML with output that still means the same thing when Python reads it.
output appears hereJSON 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.
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.
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
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.
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.