TabOnly

YAML to JSON

Convert YAML to JSON, including multi-document files, and see what the conversion changed.

input
output
output appears here

Reading 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.

shell · yq (the Go one, mikefarah/yq)
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.
python · safe_load_all, not safe_load
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.
node · the yaml package, all documents
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.
jq · the shape check before you trust any of this
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.

related tools