JSON to YAML converter

Converts JSON to YAML and explains each quote it adds — the strings that would silently become booleans, numbers or dates without one.

Input
YAML
name: deploy
country: 'NO'
startsAt: '12:30'
mode: '0755'
version: '1.10'
released: '2024-01-30'
enabled: 'yes'
script: |-
  set -e
  npm run build
  npm test
replicas: 3
tags:
  - web
  - edge
Why each string is quoted

Quoted values: 6. Needed only by YAML 1.1: 4. Switch the schema above to see the difference.

  • country1.1 only

    “NO” would be read as the boolean false.

  • startsAt1.1 only

    “12:30” would be read in base 60 as 750.

  • mode

    “0755” has a leading zero, so it would be read as the number 493.

  • version

    “1.10” would be read as the number 1.1.

  • released1.1 only

    “2024-01-30” would be read as a date rather than as text.

  • enabled1.1 only

    “yes” would be read as the boolean true.

What this tool does

It converts a JSON document into YAML, and then tells you why each string ended up in quotes. That second part is the reason the tool exists: every other converter hands back the output and leaves you to discover later that one of your values is no longer a string.

The conversion runs one way. Reading YAML is a much larger problem than writing it, and a partly-correct YAML parser is worse than none at all — it accepts your file and hands back the wrong data without complaining. Emitting is bounded, so that is the direction covered here.

Why a converter needs opinions at all

JSON says what type everything is. A string has quotes, a number does not, and there is no third possibility. YAML instead decides the type of an unquoted value by looking at its shape: if it matches the pattern for a boolean it is a boolean, if it matches a number it is a number, and only if it matches nothing is it left as text.

That is what makes YAML pleasant to write by hand and what makes converting into it a judgement call. Every string in the input has to be checked against every pattern YAML resolves, and quoted if it matches one. Get it wrong in the safe direction and the output is noisy; get it wrong in the other direction and a value silently changes type.

The Norway problem, and its relatives

The best-known case is a list of country codes. Norway is NO, and in YAML 1.1 the unquoted token NO is the boolean false. A configuration file that lists countries loses Norway and gains a false, and nothing anywhere reports an error.

It is not one odd rule but a family of them. YAML 1.1 reads all of y, Y, yes, no, on and off as booleans, in every capitalisation, which catches a chemical symbol, a switch position and an answer to a question. And the numeric resolvers are stranger still:

  • 12:30 is 750. YAML 1.1 reads colon-separated digits as base 60, so a time of day or a duration becomes an integer.
  • 0755 is 493. A leading zero means octal in YAML 1.1 — and in YAML 1.2 the same text is the decimal 755, so the two versions disagree about which number rather than about whether.
  • 1.10 is 1.1. A two-part version number is a float, and the trailing zero disappears. A dependency pinned to 1.10 now points at 1.1.
  • 2024-01-30 is a date object, not a string, because YAML 1.1 has a timestamp type.
  • An empty string is null, as are the bare words null, Null, NULL and the tilde.

None of these is a bug in YAML. They are the resolvers doing exactly what they promise, on text that happens to match. The only defence is to quote anything that matches, which is what this tool does and what the findings panel accounts for line by line.

Two versions, and why the older one is the default

YAML 1.2 arrived in 2009 and removed most of the surprising resolvers. Its core schema keeps only true and false as booleans, drops base 60 entirely, and has no timestamp type. Under 1.2, NO and 12:30 and 2024-01-30 are all just strings.

The catch is what actually reads your file. PyYAML implements YAML 1.1, and PyYAML is the parser behind an enormous amount of tooling — Ansible, older Kubernetes clients, countless scripts. Go’s yaml.v3 and the current js-yaml follow 1.2. So the same document can be read two different ways depending on who opens it, and the only output that is safe everywhere is one quoted for 1.1.

That is the default here. Switching the setting to 1.2 does not hide anything — it re-emits with the newer resolvers and the findings panel shrinks, so you can see precisely which quotes were there for the older schema. The ones that stay are the ones every parser needs.

Strings that break the syntax rather than the type

A second group of strings has to be quoted for a different reason: not because YAML would read them as another type, but because they would not parse as text at all.

  • A colon followed by a space ends a key. "note: time: now" would be read as a key "note" whose value is a key "time".
  • A space followed by a hash starts a comment, so everything after it disappears.
  • A leading -, ?, :, [, ], {, }, #, &, *, !, |, >, %, @ or backtick is an indicator character and means something structural.
  • Leading or trailing whitespace is not preserved by an unquoted value, so " x " comes back as "x".
  • A tab anywhere in the value is rejected outright — PyYAML refuses the whole document rather than mis-reading it, so this one fails loudly.

Single quotes are used wherever they suffice, because they have exactly one escape rule — an apostrophe is written twice — and are easier to read than the backslash escapes double quotes bring. Double quotes are reserved for the cases that genuinely need escaping: control characters, tabs, and multi-line strings that cannot use a block.

Multi-line strings and the chomping indicator

A string containing newlines — a script, a certificate, a block of prose — is usually why someone wants YAML in the first place. JSON can only write it with backslash-n escapes on one very long line; YAML has the literal block scalar, introduced by a pipe, where the text appears indented and readable underneath.

The subtlety is what happens to the newlines at the end, which is controlled by the chomping indicator:

  • A bare pipe clips: however many trailing newlines the block has, the value gets exactly one.
  • A pipe followed by a minus strips: the value gets none.
  • A pipe followed by a plus keeps: the value gets every one of them.

This tool picks the indicator from the string it was given, so the value survives the round trip exactly. It is worth knowing because the default — the bare pipe — is the one people write by hand, and it quietly normalises a string that ended with two newlines or none.

A block scalar cannot carry everything, and where it cannot, the output falls back to double quotes and the findings panel says why. A carriage return does not survive, because block scalars normalise line breaks. A first line beginning with a space would be read as extra indentation and stripped. And a line ending in a space is preserved by the specification but invisible on screen and removed by most editors on save, so quoting it is the safer choice — that one is a decision rather than a limitation, and it is reported as one.

More than one document

YAML has something JSON does not: a file can hold a stream of documents separated by three dashes. That is the format of a Kubernetes manifest, and it is why a JSON array so often needs to become something other than a YAML sequence.

The switch here emits each element of a top-level array as its own document. And when the input is not a single JSON value at all but every line parses on its own, it is read as NDJSON — the line-delimited format that logs and API exports arrive in — and each line becomes a document. That reading is a guess, so it is reported above the output rather than made silently.

The fallback only applies when the whole input fails to parse and every line succeeds, which a merely malformed document will not do. A syntax error still surfaces as a syntax error, with the line and column it occurred on.

What the conversion cannot preserve

Two things are lost before this tool sees your data, both in the JSON parse itself, and it is worth knowing which.

Duplicate keys. JSON permits an object to list the same key twice and most parsers keep the last one silently. YAML forbids duplicates outright, so the output will be valid, but the earlier value is already gone — and no converter can report what it never received.

Integer precision. A JSON number larger than about nine quadrillion does not survive being parsed into a double, so an ID like 12345678901234567890 comes back rounded. This is not a YAML problem and not a problem this tool introduces; it happens in every JSON parser in the language. If a large identifier matters, it belongs in a string on both sides.

Notes on the output

Indentation is spaces, always, because YAML forbids tabs for indentation entirely — that is one of the few things the format is strict about. Two spaces is the convention; four is offered because some house styles want it.

Sequences are indented under their key. Both that and the un-indented form are legal YAML and mean the same thing; the indented one is what most people write and what most editors fold correctly.

An empty array or object is written in flow style as a pair of brackets or braces, because block style has no way to express emptiness — there is nothing to write on the following lines.

The output ends with a newline, and that is load-bearing rather than tidiness. A block scalar’s chomping is measured against the line break that follows it, so a clipped block at the very end of a file without a final newline loses the newline it was supposed to keep.

Frequently asked questions

Is JSON already valid YAML?
Under YAML 1.2, yes: the specification says so explicitly, and a 1.2 parser will read a JSON file directly. That is not much use in practice, because the reason to convert is readability — comments, block scalars, no braces — and pasting JSON into a YAML file gets you none of it. Under YAML 1.1 it is not quite true, which is one more reason the two versions are worth telling apart.
Why did my string get quotes it does not seem to need?
It almost certainly needs them. YAML types an unquoted value by matching patterns, so NO, yes, off, 12:30, 0755, 1.10, 2024-01-30 and an empty string all stop being strings. The findings panel names every one and shows the value it would have become, so the claim can be checked rather than trusted. If you are targeting a YAML 1.2 parser, switching the schema removes the ones only 1.1 needs.
What is the difference between YAML 1.1 and 1.2 here?
1.2 dropped the resolvers that cause most of the surprises: yes/no/on/off are no longer booleans, base 60 is gone, and there is no timestamp type. PyYAML implements 1.1 and is still everywhere, so the conservative output is the default; the 1.2 setting is there for when you know what will read the file.
Can it convert YAML back to JSON?
No, deliberately. A YAML reader needs anchors, aliases, tags, merge keys, five scalar styles and two schema versions, and getting any of it subtly wrong means accepting a file and returning different data than it contained. That failure is silent, which makes it worse than not offering the feature.
How do I get a Kubernetes-style multi-document file?
Turn on the switch that emits each element of a top-level array as its own document, and the array becomes documents separated by three dashes. If your input is NDJSON instead — one JSON object per line, as logs and API exports often are — that is detected automatically and reported above the output.
Why are there no comments in the output?
Because there were none in the input. Comments are the main thing YAML has that JSON lacks, and a converter cannot invent them. This is worth remembering in the other direction too: if you round-trip a YAML file through JSON, every comment in it is gone.
Is anything I paste sent to a server?
No. Parsing and conversion run entirely in your browser; nothing is uploaded or logged, and it works with no network connection.