All three describe the same thing: nested maps and lists of strings, numbers and booleans. Converting between them is mechanical. So the choice is not about capability, it is about who is going to be typing.
JSON is for transmission
It was designed to be produced and consumed by programs, and everything about it follows from that. The syntax is strict, so there is exactly one way to read any document. Braces and brackets mean whitespace does not matter. Every parser agrees.
The strictness is a feature in transit and a burden in a file someone maintains. No comments. No trailing commas — the one that costs the most, because adding a line to the end of a list means editing the line above it. Keys must be double-quoted. Nothing is forgiving.
For an API response, a log line, or anything a machine writes, that is exactly right.
YAML is for editing
Indentation replaces braces, quotes are usually optional, comments are allowed, and long strings can be written across several lines without escaping. A YAML file reads like a document, which is why it took over configuration.
Everything that makes it pleasant also makes it easy to get subtly wrong. Indentation is significant, so one stray space changes the structure. A tab character is a syntax error, and tabs are invisible. The spec is famously large, and parsers differ at the edges.
The type inference that keeps catching people
This is the specific thing worth knowing before you write any YAML.
An unquoted value is interpreted rather than taken literally. Under YAML 1.1 — still what many deployed parsers implement — NO becomes the boolean false, which memorably turns Norway's country code into false. ON, OFF, YES and Y are booleans too.
Numbers are worse. A version written as 1.20 becomes the number 1.2 and loses its trailing zero. A value beginning with a zero may be read as octal. Something like 6e2 — a plausible git hash prefix — becomes 600. A MAC address with colons can be read as a sexagesimal number.
The fix is one character: quote anything that is meant to be a string. It is worth doing by default rather than when you notice.
TOML is for configuration that is mostly flat
Comments, obvious syntax, no significant whitespace, and no surprise type inference — values mean what they say. For an application's settings file it is the easiest of the three to get right.
It becomes awkward with deep nesting, where the table-header syntax turns into long dotted names, and with long lists of objects. That is precisely where YAML stays readable, which is why deployment and pipeline configuration is YAML and package metadata is increasingly TOML.
How to choose
A machine writes it and a machine reads it — JSON. Fastest to parse, universally supported, no ambiguity.
A person edits it and it has real structure — YAML. Pipelines, deployments, anything with lists of steps.
A person edits it and it is mostly settings — TOML.
It has to be read by both — JSON, with the configuration generated from something else. Hand-maintaining JSON is where the missing comments hurt most.
Converting between them
The conversion is lossy in one direction and only one: comments do not survive. Converting YAML to JSON discards every explanatory note in the file, and converting back does not restore them.
That is worth stating because it is not obvious and it is irreversible. If a YAML file is documented — and a good one is — treat it as the source and generate the JSON, rather than round-tripping.
Converting is otherwise mechanical, and formatting a JSON document is worth doing before reading one — a minified response is a wall, and the same data indented is usually obvious.
The short version
JSON for transmission, YAML for structured configuration a person maintains, TOML for settings. Quote your YAML strings. And remember that converting away from YAML throws the comments away for good.