YAML to JSON
Convert YAML to JSON, including multi-document files, and see what the conversion changed.
output appears hereReading YAML is a harder problem than writing it
Converting JSON to YAML is a question of which bytes to
emit. Converting YAML to JSON is a question of what a document means, and
YAML has two documented, incompatible answers to that question. This page parses
under YAML 1.2, the spec the yaml library defaults to: bare
no, on, and 12:34:56 stay strings. PyYAML and
other YAML 1.1 implementations resolve those same spellings as booleans or a
sexagesimal number — measured this session, six of nine common tokens changed
type depending on which spec version read them. If a value here looks different
from what a Python script produced from the same file, that difference is real and
it is why this page exists.
Kubernetes manifests, GitLab CI files, and multi-document exports commonly pack
several YAML documents into one file, separated by ---. A converter
built on a single-document parse call throws outright on that shape; this page
always parses with the multi-document API, so it handles a file with a Service and
a Deployment in it the same way it handles a file with one document — you
choose whether the result is one JSON array or a sequence of JSON documents. Merge
keys (<<) are applied automatically, matching what Docker
Compose and GitLab CI both expect from the same syntax, rather than left as a
literal key in the output.
Some values genuinely have no JSON form. .inf, -.inf, and
.nan parse correctly inside this page's code, but JSON has no
non-finite number (RFC 8259 §6), so they become null on the
way out, and the page reports which paths that happened to rather than letting it
pass silently. Duplicate keys are rejected with a line and column — YAML is
stricter here than JSON, which is the opposite of what most people assume —
and a rejection never clears the rest of the conversion: the parts that did parse
stay on screen next to the error. Integers past 253 can be kept as JSON
strings instead of silently rounding, and a nested alias chain designed to expand
into billions of copies (a "billion laughs" attack) is rejected by the parser's
built-in limit — there is no setting on this page that turns that protection
off.
Do it without this tool
Same conversion, your own tools — and the multi-document flag is the one that trips people up in every one of them.
yq -o=json manifest.yml # single document yq -o=json ea '[.]' multi-doc.yml # multiple documents into one JSON array # ea = "evaluate all" — without it yq only emits the FIRST document # in a multi-document file and silently drops the rest.
import json, yaml
with open("manifest.yml") as f:
docs = list(yaml.safe_load_all(f)) # plural — safe_load() throws on
# multi-document input, same as
# YAML.parse() does in JS
print(json.dumps(docs if len(docs) > 1 else docs[0], indent=2))
# safe_load / safe_load_all, never plain load(): load() can construct
# arbitrary Python objects from YAML tags, which is a real attack surface
# on a file you did not write yourself.
import YAML from 'yaml';
const docs = YAML.parseAllDocuments(source, { merge: true });
const values = docs.map((doc) => doc.toJS());
JSON.stringify(values.length > 1 ? values : values[0], null, 2);
// parseDocument/parseAllDocuments report errors alongside a partial
// value; YAML.parse() just throws and gives you nothing to work with.
yq -o=json manifest.yml | jq 'paths(type == "boolean")' # Lists every path yq resolved to a boolean — the fastest way to spot a # "no" or "on" that got read as false/true instead of staying a string.
Why the same file means two things
YAML 1.2.2 §10.2 defines the core schema this page parses under: a small,
closed set of literal spellings for null, true, false, and numbers, under which
no is not one of them. YAML 1.1 §10.3 defines the older,
larger resolution table — the one PyYAML, Ruby's Psych, and
gopkg.in/yaml.v2 implement — where no/
yes/on/off and a bare
HH:MM:SS token all resolve to something other than a plain string.
RFC 8259 defines the JSON this page writes, and §6 is why
.inf, -.inf, and .nan — all valid
YAML — have no representation and become null: JSON's number
grammar has no syntax for a non-finite value. None of this is a defect in either
spec; it is two specs making different, both-defensible choices, and a converter
that doesn't say which one it made is the actual problem.
FAQ
Why did no stay a string here instead of becoming false?
This page parses under YAML 1.2, and YAML 1.2's core schema resolves bare
no as the ordinary string "no", not a boolean. PyYAML
— the library behind most Python YAML tooling — still implements
YAML 1.1, whose resolver treats no/yes/
on/off as booleans, which is why the same file can come
out differently depending on which tool reads it. Measured against PyYAML 6.0.3:
of nine representative tokens (no, yes, on,
off, 12:34:56, 0755, 007,
1_000, y/n), six resolve to a different
type under 1.1 than under 1.2. If you need the 1.1 reading to sanity-check a value
before it reaches a Python consumer, that is a different question than this page
answers — it converts, it does not emulate a second parser.
Why did the converter handle a Kubernetes file with --- in it, when others fail?
A --- separator marks the start of a new YAML document, and a
Kubernetes manifest with a Service and a Deployment in
one file is two documents in one string — the normal shape for
kubectl apply -f. A converter built on YAML's single-document parse
call throws outright on that input; this page always parses with the
multi-document API, so it never hits that error class in the first place. What
differs is how the documents become JSON: the array setting (the
default) puts every document into one JSON array, and the separate
setting emits one JSON value per YAML document, joined by its own
--- markers, matching the shape of the source file. A trailing empty
document — the null document a lone closing --- produces
— is dropped automatically rather than showing up as an extra
null entry.
What happened to the << merge key?
It was applied, not carried over literally. YAML's merge key
(<<) is how Docker Compose and GitLab CI files implement
inheritance — base: &b {p: 1} then
c: {<<: *b, q: 2} means c should end up
with both p and q. This page always resolves that merge
before producing JSON, so c comes out as
{"p":1,"q":2}, matching what a tool that understands merge
keys would expect, rather than as a literal "<<" key holding
the anchor's contents. If you were expecting to see the merge key itself in the
JSON — to inspect the inheritance rather than resolve it — that is
not a mode this page offers; it always flattens.
Why is a duplicate key an error here, when the JSON version wouldn't reject it?
YAML is the stricter format on this specific point, which is the opposite of what
most people expect. JSON.parse('{"a":1,"a":2}') silently
keeps the last value with no warning, because the JSON grammar doesn't forbid a
duplicate key. YAML's spec does, and the parser used here reports it as a
DUPLICATE_KEY error with the exact line and column. This page does
not fail the whole conversion over it: the rest of the document still converts,
the partial result stays visible, and the error names where the problem is
instead of replacing your output with a generic “invalid YAML”
message.
Where did the comments go?
JSON has no comment syntax (RFC 8259 defines its grammar with no such production), so there's no place in the output to put them — this is a property of the target format, not a bug in the conversion. It's also the one loss almost everyone already expects going into a YAML-to-JSON conversion, unlike the merge-key and duplicate-key behavior above, which is why it gets one paragraph here instead of its own investigation. If you need to preserve a YAML file's comments through an edit, that has to stay a YAML-to-YAML operation — nothing downstream of this page's JSON output can bring them back.