YAML vs JSON for Configuration: Syntax, Pitfalls and Conversion
Compare YAML and JSON for configuration: syntax, the Norway problem, safe loading, and what survives conversion, with output from real parsers you can run.

Sooner or later every project asks the same question: should this configuration file be YAML or JSON? Your Kubernetes manifests are YAML, your package.json is JSON, your CI pipeline is YAML again, and your API gateway wants JSON. Developers move text between the two formats all day, and the move is rarely as innocent as it looks. A country code turns into a boolean, a version number loses a zero, a comment vanishes, and a whole file fails because of one invisible tab. This guide compares the two formats side by side, explains how YAML really works under the surface, walks through the classic traps with output from real parsers, and shows how to convert between the formats safely. It is written for developers, DevOps engineers and anyone who edits configuration by hand.
What are YAML and JSON?
JSON is a small data format with six kinds of value: objects, arrays, strings, numbers, booleans and null. Its grammar is strict enough to fit on a single page. Keys are always double-quoted strings, commas separate items, braces and brackets mark structure, and there are no comments. It descends from the JavaScript object literal syntax and became the default format for web APIs because every language can parse it and every parser agrees about what valid input looks like. Our JSON formatting guide covers its syntax in depth.
YAML is a much larger language. The name started life as "Yet Another Markup Language" and was later reinterpreted as "YAML Ain't Markup Language" to stress that it describes data, not documents. It uses indentation instead of braces, allows comments, supports several ways to write strings, and adds features JSON does not have at all: anchors and aliases for reuse, multiple documents in one file, and explicit type tags. The current specification is YAML 1.2.2, a revision that clarifies the 1.2 text without changing the language.
A short history
JSON was popularised in the early 2000s as a lightweight alternative to XML for sending data between browsers and servers. YAML appeared at around the same time with a different goal: a data format that people would enjoy writing by hand, aimed at configuration files and at languages such as Perl, Python and Ruby. Both formats spread through the scripting-language world, and each found a home. JSON won the network, because machines produce and consume it easily. YAML won the configuration file, because humans read and edit it easily.
The most important event in YAML's history for everyday developers is the move from version 1.1 to version 1.2. Version 1.2, published in 2009, was designed so that JSON became valid YAML, and it tightened the rules that decide which plain words turn into booleans or numbers. Many popular libraries still implement the older 1.1 rules, or a mixture of both, and that gap is the source of most YAML horror stories. We will return to it in the pitfalls section.
The two formats at a glance
Here is the same small service description in both formats. First JSON:
{
"service": "web",
"image": "nginx:1.25",
"ports": ["80:80", "443:443"],
"environment": {
"LOG_LEVEL": "info",
"DEBUG": false
}
}
And the YAML equivalent, with a comment that JSON cannot hold:
# Public web front end
service: web
image: nginx:1.25
ports:
- "80:80"
- "443:443"
environment:
LOG_LEVEL: info
DEBUG: false
The YAML version is shorter and has no punctuation noise, and it carries documentation next to the data. The JSON version is longer, but there is only one way to read it. That trade-off, comfort for humans against predictability for machines, runs through the rest of this article.
How YAML syntax works
YAML has three structural building blocks: mappings (key and value pairs, called objects in JSON), sequences (ordered lists, called arrays in JSON) and scalars (single values such as strings, numbers and booleans). Everything else is a way of writing those three.
Indentation defines structure
In block style, nesting is expressed with spaces. A child is indented further than its parent, and siblings share the same indentation. The exact width does not matter as long as it is consistent within a level, but two spaces is the near-universal convention.
server:
host: localhost
ports:
- 8080
- 8443
tls:
enabled: true
Tabs are not allowed for indentation. This is a deliberate rule in the specification, since tabs render at different widths in different editors and would make structure ambiguous. A tab character at the start of a line is therefore a syntax error, which we will reproduce shortly.
A sequence item starts with a dash and a space. A mapping inside a sequence item continues on the following lines, aligned with the first key:
users:
- name: ada
role: admin
- name: grace
role: editor
Scalars and implicit typing
A scalar written without quotes is called a plain scalar, and the parser decides its type by looking at it. The text 42 becomes an integer, 3.14 a float, true a boolean, null or ~ a null, and most other text a string. This is convenient, because you rarely need quotes, and dangerous, because the rules depend on the YAML version and on the library. Quoting a value always makes it a string:
plain_number: 42
quoted_number: "42"
plain_bool: true
quoted_bool: "true"
nothing: null
also_nothing: ~
Single quotes keep every character literally, with '' standing for one single quote. Double quotes understand escape sequences such as \n, \t and é, much like JSON strings do.
Comments
A comment starts with # and runs to the end of the line. In a plain scalar, the # must be preceded by a space to begin a comment, so x#y is the string x#y while x #y is the string x followed by a comment. We checked this with PyYAML: the value b # c loaded as b, the quoted value "x # y" kept its hash, and x#y stayed intact.
Comments are the single most common reason teams choose YAML for configuration. A comment can explain why a timeout is 30 seconds, link to the ticket behind a feature flag, or warn that a value must match another file. JSON has no equivalent, and workarounds such as a "_comment" key pollute the data.
Block style and flow style
YAML offers two styles that can be mixed. Block style uses indentation and line breaks. Flow style uses braces and brackets, and looks exactly like JSON:
# Block style
tags:
- alpha
- beta
limits:
cpu: 2
memory: 512
# Flow style, same data
tags: [alpha, beta]
limits: {cpu: 2, memory: 512}
Flow style is handy for short lists, and it is the reason YAML can read JSON. Flow collections can span several lines, and inside them indentation matters less, but commas and brackets must balance exactly as in JSON. Unlike JSON, flow style allows unquoted keys and strings, and permits comments.
Multi-line strings: literal and folded
Long text is where YAML shines. A literal block scalar, introduced by |, keeps line breaks exactly as written. A folded block scalar, introduced by >, joins lines with spaces and treats a blank line as a paragraph break. We ran both through PyYAML:
import yaml
print(repr(yaml.safe_load("a: |\n line1\n line2\n")))
print(repr(yaml.safe_load("a: >\n line1\n line2\n\n para2\n")))
The output was:
{'a': 'line1\nline2\n'}
{'a': 'line1 line2\npara2\n'}
Notice the trailing newline in both results. By default a block scalar keeps exactly one final newline. A "chomping indicator" after the style character changes that: |- and >- strip the final newline, and |+ keeps all trailing blank lines. Running a: |- with the lines x and y gave 'x\ny', and a: >- gave 'x y'.
Use | for shell scripts, certificates and anything where line breaks matter. Use > for long prose that you want wrapped in the source file but joined in the data. JSON has no equivalent: a multi-line string must be written on one line with \n escapes, which is miserable for scripts embedded in a CI pipeline.
Anchors, aliases and merge keys
YAML can name a node with an anchor (&name) and reuse it elsewhere with an alias (*name). This removes duplication in files that repeat the same block many times:
base: &base
retries: 3
timeout: 30
dev:
<<: *base
timeout: 5
prod: *base
The special key << is the merge key. It copies the entries of the aliased mapping into the current one, and explicitly written keys win, so dev overrides timeout. The merge key is a convention from the YAML 1.1 era that is documented outside the core 1.2 specification, but many libraries support it, and tools such as Docker Compose and GitLab CI rely on it. Loading the example with PyYAML and printing it as JSON gave:
{"base": {"retries": 3, "timeout": 30}, "dev": {"retries": 3, "timeout": 5}, "prod": {"retries": 3, "timeout": 30}}
The alias relationship is gone. JSON has no references, so each use is expanded into a full copy. That matters for conversion, as we will see.
Multiple documents in one file
A line containing only --- starts a new document, and ... optionally ends one. A single YAML file can therefore hold several independent documents, which is how Kubernetes users keep a Deployment and a Service in one file. PyYAML's safe_load_all read a: 1, ---, b: 2, ---, - x as three documents: [{'a': 1}, {'b': 2}, ['x']]. JSON has no such concept. A file holds exactly one value, and teams that need several usually switch to newline-delimited JSON or wrap them in an array.
YAML is a superset of JSON, with caveats
From version 1.2 onward, the specification describes YAML as a superset of JSON. In practice this means a JSON document is, with a couple of exceptions, a valid YAML document. We confirmed it with PyYAML by loading a JSON string with nested arrays, an object, a null and a boolean, and the parser returned the same structure. That makes migration easy in one direction: you can rename a .json file to .yaml and then gradually rewrite parts of it in block style.
The word "superset" hides a few details worth knowing.
- Parser versions differ. A parser that implements the 1.1 rules may interpret a JSON string or number slightly differently from a strict JSON parser.
- Duplicate keys. JSON says object names should be unique but does not make duplicates a grammar error, and most parsers keep the last value. YAML 1.2 says keys must be unique. Libraries differ in whether they enforce it, and we will test this below.
- Extra types. A YAML parser may return dates, sets or binary data, which JSON cannot represent. Converting such a document to JSON needs a decision about how to write them.
- The reverse is not true. YAML features such as comments, anchors and block scalars have no JSON form at all.
Think of JSON as the safe subset of YAML that every tool understands. If you restrict yourself to it, you get the predictability of JSON; if you use the full language, you inherit the complexity.
Where each format is used
Both formats are everywhere, and the pattern follows a clear logic: files that humans maintain lean towards YAML, and data that machines exchange leans towards JSON.
Kubernetes. Manifests are normally written in YAML. The API server itself speaks JSON, and manifests can be written in either format, but nearly every example and tutorial uses YAML because it is more pleasant to read and allows comments.
Docker Compose. The compose.yaml file describes services, networks and volumes. YAML's anchors and merge keys are frequently used to share settings between services.
GitHub Actions and other CI systems. Workflows live in YAML files. Multi-line run: | blocks make embedded shell scripts readable, which would be painful in JSON.
OpenAPI and AsyncAPI. An OpenAPI description can be written in YAML or JSON, and the two are interchangeable. Teams often author in YAML for comments and readability and publish JSON for tools that only read JSON.
package.json, composer.json and similar manifests. Package managers use JSON, partly because tools edit these files programmatically, and a strict format makes automated rewriting safe.
tsconfig.json. TypeScript's configuration file is a good example of the middle ground. The extension says JSON, but the compiler accepts comments and trailing commas in it. That relaxed dialect is often called JSONC (JSON with comments). Editors treat it as a special case, so a plain JSON parser will reject the same file.
Application settings. Frameworks split between the two. Many accept both, and the choice is usually made by the team's habits.
Human editing versus machine generation
A useful rule of thumb comes from asking who writes the file. If a person edits it, favour a format with comments and low punctuation. If a program writes and reads it, favour a strict format with unambiguous parsing. YAML leans towards the first, JSON towards the second. When both apply, such as a config that is hand-edited but also rewritten by tools, you need to know that most YAML tools discard comments and formatting when they rewrite a file. The comment you carefully added last week will disappear the first time a script updates the file, unless the library is built to preserve comments.
Classic YAML pitfalls
Most of YAML's reputation for surprises comes from implicit typing. The parser guesses a type from unquoted text, and the guess depends on the rules the library follows. Here is a real experiment. We loaded the following document with PyYAML's safe_load:
country: NO
answer: no
flag: yes
switch: on
off_value: off
version: 1.10
py: 3.10.1
perm: 0755
perm2: 0o755
clock: 1:30
zip: 02134
nothing: ~
empty:
date: 2026-03-14
quoted: "no"
The result, printed with each value's Python type:
'country': False (bool)
'answer': False (bool)
'flag': True (bool)
'switch': True (bool)
'off_value': False (bool)
'version': 1.1 (float)
'py': '3.10.1' (str)
'perm': 493 (int)
'perm2': '0o755' (str)
'clock': 90 (int)
'zip': 1116 (int)
'nothing': None (NoneType)
'empty': None (NoneType)
'date': datetime.date(2026, 3, 14) (date)
'quoted': 'no' (str)
Almost every line contains a lesson. Let us take them in turn.
The Norway problem
The country code for Norway is NO. In YAML 1.1, the plain words yes, no, on, off, true and false, in several capitalisations, are all booleans. PyYAML therefore turned country: NO into False. A list of country codes such as [de, no, fr] loaded as ['de', False, 'fr'] in our test. This is the famous "Norway problem", and it hits real data: country codes, short answers, feature switches and any column that happens to contain the word "no".
The fix is to quote any value that must stay a string: country: "NO". Libraries that implement YAML 1.2's core schema only treat true and false as booleans, so NO stays a string there. The spec made that change deliberately, but you cannot assume your library did the same. The safest habit is to quote strings that look like anything other than plain words.
A related surprise affects GitHub Actions workflows. The trigger key is spelled on, and under 1.1 rules that key is itself a boolean. We loaded a typical on: push block with PyYAML and got {True: {'push': {'branches': ['main']}}}. GitHub's own parser handles it correctly, but a script that reads workflow files with a 1.1 library will find the key True instead of "on". If you write tooling around such files, expect that.
Interestingly, PyYAML did not turn the single letters y and n into booleans, even though the 1.1 type list mentions them, which shows that libraries implement different subsets of the rules. Do not memorise a table; quote when in doubt.
Version numbers and trailing zeros
version: 1.10 loaded as the float 1.1. The trailing zero is gone, and the value now represents a different release if you meant version 1.10 of a library. Compare that with py: 3.10.1, which has two dots and so cannot be a number, and stayed a string. The same trap catches Docker image tags, runtime versions in CI matrices (a matrix of 3.8, 3.9, 3.10 yields 3.1 for the last entry), and chart versions. Always quote versions: version: "1.10".
Octal, sexagesimal and other old number forms
YAML 1.1 treats a leading zero as octal. perm: 0755 loaded as 493, which is the decimal value of octal 755. That is helpful for file permissions and harmful for postal codes: zip: 02134 became 1116, because the digits are valid octal. A zip code that contains an 8 or a 9, such as 08540, would not be valid octal and might load as a string or number depending on the library, which makes the problem inconsistent and hard to spot.
YAML 1.1 also defines sexagesimal numbers, base-60 values written with colons. clock: 1:30 loaded as 90, one minute and thirty seconds in minutes-and-seconds notation. A time such as 12:30 in an unquoted field can become 750. A port mapping like 80:80 can fall into the same trap in some parsers, which is why Docker Compose documentation recommends quoting port mappings. In our earlier example we quoted them: "80:80".
Version 1.2 dropped octal-by-leading-zero, using the explicit 0o755 form, and dropped sexagesimal. In our test the 1.1-style PyYAML left 0o755 as a plain string. Underscores in numbers are another 1.1 relic: PyYAML read 1_000 as 1000.
Dates become date objects
date: 2026-03-14 loaded as a datetime.date object, not a string. In Python that is helpful until you try to serialise the result to JSON, where dates are not allowed. The conversion function we used later in this article needed default=str to cope. If you want a string, quote the value.
Empty values are null
A key with nothing after the colon, like empty:, is null, not an empty string. If you need an empty string, write empty: "". Both the tilde and the empty value are null in YAML.
Tabs are forbidden
Indenting with a tab is a hard error. PyYAML reported:
while scanning for the next token | found character '\t' that cannot start any token
in "<unicode string>", line 2, column 1
The trouble is that tabs are invisible. A file that looks perfectly aligned in your editor can contain one, usually pasted from a web page or typed by a colleague whose editor inserts tabs. Configure your editor to insert spaces for YAML files and to show whitespace characters.
Indentation mistakes
Misaligned lines either produce an error or, worse, silently change the structure. In our test, a: 1 followed by an extra-indented b: 2 gave "mapping values are not allowed here" at line 2, column 4. A more insidious case is a key that is indented one level too deep or too shallow but still valid, which attaches it to the wrong parent. A Kubernetes field in the wrong place may be ignored or rejected, depending on validation. Always check the structure after editing, ideally with a schema validator or the server's dry-run mode.
Special characters need quotes
A plain scalar cannot contain a colon followed by a space, because that begins a mapping. Writing msg: hello: world failed with "mapping values are not allowed here". Other values that need quotes include text that starts with *, &, !, @, %, {, [, >, |, #, or a backtick, since those characters begin aliases, anchors, tags, directives, flow collections, block scalars or comments. Our test with k: @x produced a scanner error, and k: *x failed because an alias pointing at an undefined anchor is invalid. The practical rule: if a value contains punctuation, wrap it in quotes. Prefer single quotes for values that contain backslashes, such as Windows paths or regular expressions, because single-quoted strings do not process escapes.
Duplicate keys
JSON parsers silently keep the last duplicate. YAML 1.2 forbids duplicates, but not every library checks. PyYAML loaded a: 1 followed by a: 2 without complaint and returned {'a': 2}. This is the dangerous version of the bug: a copy-pasted block with a repeated key means the first value is silently discarded. Node's JSON.parse('{"a":1,"a":2}') behaves the same way, returning { a: 2 }. Use a linter that flags duplicate keys, because neither format's common parsers will warn you.
Strings that look like other things
Besides booleans and numbers, watch for null-like words (Null, NULL, ~), special floats such as .inf and .nan, and hexadecimal such as 0x1F, which PyYAML read as 31. A password or token that happens to look like a number or keyword can be mangled. When a value is an identifier, quote it, whatever it looks like.
Security: safe loading versus arbitrary object construction
The most serious YAML hazard has nothing to do with typing. Some YAML libraries can construct arbitrary objects from tags in the document. In Python's PyYAML, the full (unsafe) loader understands tags such as !!python/object/apply, which call arbitrary functions. That means loading untrusted YAML with the wrong function can execute code on your machine. We demonstrated it with a harmless command:
import yaml
evil = "!!python/object/apply:os.system ['echo PWNED']"
try:
yaml.safe_load(evil)
except yaml.YAMLError as e:
print("safe_load refused:", type(e).__name__)
yaml.unsafe_load(evil) # prints PWNED
The real output was:
safe_load refused: ConstructorError
PWNED
safe_load rejected the tag with a ConstructorError, while the unsafe loader ran the command and printed PWNED. Modern PyYAML versions require you to name a loader explicitly: calling yaml.load(text) without one raised TypeError: load() missing 1 required positional argument: 'Loader' in version 6.0.3, which is the version we tested. Older tutorials show yaml.load(text) and are a classic source of vulnerable code.
The rules to follow:
- Always use the safe loader (
yaml.safe_load) for anything you did not write yourself, and by default for everything. - Never load untrusted YAML with a loader that supports language-specific tags. Other languages have the same trap, so check your library's documentation for its default loader and its safe mode.
- Treat parsed configuration as untrusted input and validate its shape against a schema before using it.
- Beware resource exhaustion. Aliases allow a small document to expand into an enormous structure, in the same spirit as the "billion laughs" attack against XML. Cap the size of untrusted documents and the depth of nesting.
JSON has no equivalent of object-construction tags. A standard JSON parser only creates plain data. That simplicity is a real security advantage for formats that cross trust boundaries, and it is one more reason why public APIs use JSON while YAML is kept for files you control. For related thinking on what to cap and validate when handling untrusted payloads, see our guide to JSON minification and payload size.
Converting between YAML and JSON
Conversion is mechanical when the data is plain, and lossy when it is not. Before you convert, know what survives.
| Feature | YAML to JSON | JSON to YAML |
|---|---|---|
| Comments | Lost, JSON cannot store them | Nothing to preserve |
| Anchors and aliases | Expanded into full copies | Not generated |
| Merge keys | Resolved into explicit keys | Not generated |
| Key order | Usually preserved by modern tools | Preserved unless the tool sorts keys |
| Multiple documents | Must become an array or separate files | Single document only |
| Dates and timestamps | Need a string representation | Come out as plain strings |
| Block scalars (pipe and greater-than styles) | Become strings with \n |
May be emitted as quoted or block strings |
| Unquoted ambiguous words | Typed by the parser, for example no becomes false |
Strings that look like booleans or numbers should be quoted |
Our DevUtilX tools handle the common cases for you: the YAML to JSON converter turns a YAML document into formatted JSON, and the JSON to YAML converter goes the other way. Both run locally in your browser.
Round-tripping: what the data looks like afterwards
We tested the JSON to YAML direction with a small file, using PyYAML's safe_dump:
import json, yaml
src = '{"name": "web", "image": "nginx:1.25", "ports": ["80:80"], "debug": false, "version": "1.10", "answer": "no"}'
print(yaml.safe_dump(json.loads(src), sort_keys=False, allow_unicode=True))
The output:
name: web
image: nginx:1.25
ports:
- 80:80
debug: false
version: '1.10'
answer: 'no'
Two details deserve attention. First, the converter quoted '1.10' and 'no' automatically. A good YAML emitter quotes any string that a parser would otherwise read as a different type, and this is what keeps the round trip lossless. Parsing the output again and comparing it with the original JSON gave True. Second, "80:80" was written without quotes, which is valid for PyYAML's rules but exactly the kind of value that other 1.1 parsers might read as a sexagesimal number. When your output feeds a different tool, consider forcing quotes on strings.
Another default surprise: PyYAML's safe_dump sorts keys alphabetically unless you pass sort_keys=False. Dumping {"b": 1, "a": 2} gave a: 2 and then b: 1. Sorting is harmless for data, and annoying for configuration where the order tells a story. Remember the argument when you write conversion scripts. Also note that PyYAML's default list style puts the dash at the same indentation as the parent key, as in the ports output above. This is valid YAML, even if many style guides prefer the extra indent.
Key order is a convention, not a guarantee
The JSON specification calls an object an unordered collection, and YAML mappings are unordered in the same way. In practice, modern parsers preserve the order they read, and people depend on it. One JavaScript quirk breaks that expectation: object keys that look like array indexes are ordered numerically first. In Node, JSON.stringify({"2":"a","1":"b","x":"c"}) printed {"1":"b","2":"a","x":"c"}. If you convert through a JavaScript object, numeric-looking keys can move. Do not write configuration that depends on the order of mapping keys.
Types after conversion
JSON has no date type, so YAML dates must become strings. Our test converted when: 2026-03-14 and ts: 2026-03-14 09:30:00 using default=str, which produced "2026-03-14" and "2026-03-14 09:30:00". Both survive, but the second is not ISO 8601 with a T, so downstream code may be unable to parse it. Decide on a canonical format and quote values you want kept as text.
Big numbers need care as well. JavaScript parses JSON numbers as double-precision floats, so JSON.parse("12345678901234567890") followed by JSON.stringify gave 12345678901234567000 in Node, as discussed in our JSON minification guide. If a YAML file carries large identifiers, write them as quoted strings.
Finally, what JSON cannot represent at all: undefined, functions, NaN and infinities. In Node, serialising {a: undefined, b: () => 1, c: NaN, d: new Date(0)} printed {"c":null,"d":"1970-01-01T00:00:00.000Z"}, silently dropping two keys and turning NaN into null. YAML's .nan and .inf have no direct JSON form, which is another reason to validate numbers before you convert.
Step-by-step: convert YAML and JSON with DevUtilX
For a quick conversion with no code or setup, use the browser tools. The input never leaves your machine, because the conversion runs locally, which matters when your configuration contains hostnames, tokens or customer data.
YAML to JSON
- Open the YAML to JSON tool.
- Paste your YAML into the input editor. Multi-document files should be converted one document at a time.
- Run the conversion.
- Read the JSON output. Check that values you quoted are still strings, and that unquoted words such as
noor1.10came out with the types you intended. - Copy the result, or use the JSON formatting guide to tidy it further for your project.
If the tool reports an error, it points at the position the parser stopped. The usual causes are a tab, a mis-indented line, or a colon inside an unquoted value, all covered in the pitfalls section.
JSON to YAML
- Open the JSON to YAML tool.
- Paste valid JSON. Comments and trailing commas are not valid JSON, so remove them first.
- Run the conversion.
- Review the YAML. Look for strings that have been quoted because they resemble booleans or numbers, which is the converter protecting your data.
- Copy the result into your repository, and add the comments that JSON could not carry.
Keep the cheatsheet nearby
YAML has more syntax than most people remember, from chomping indicators to merge keys. The YAML cheatsheet collects the common forms on one page, which saves a trip to the specification when you cannot recall whether folded scalars use > or |.
Validating YAML and JSON in code
You can script the same checks that the browser tools perform. This Python function loads a YAML file safely and reports the line and column of an error, which is useful in a pre-commit hook or a CI job:
import sys, yaml
try:
yaml.safe_load(open(sys.argv[1]))
print("ok")
except yaml.YAMLError as e:
m = getattr(e, "problem_mark", None)
print("error line", m.line + 1, "column", m.column + 1, "-", e.problem)
We ran it on a valid file and got ok. On the file a: 1 followed by b: 2, it printed error line 2 column 4 - mapping values are not allowed here. PyYAML counts lines and columns from zero internally, so the script adds one to match what editors show.
A conversion helper in the same language needs only a few lines:
import json, yaml
def yaml_to_json(text):
return json.dumps(yaml.safe_load(text), indent=2, default=str)
def json_to_yaml(text):
return yaml.safe_dump(json.loads(text), sort_keys=False, allow_unicode=True)
Running yaml_to_json on a document with a comment, a date and an integer gave:
{
"when": "2026-03-14",
"ts": "2026-03-14 09:30:00",
"n": 3
}
The comment disappeared, as expected. On the JavaScript side, plain Node can check the JSON half of the pipeline. The built-in parser gives useful error messages, and it rejects the extras that people accidentally copy from YAML or JSONC files:
try { JSON.parse('{"a":1,}'); } catch (e) { console.log(e.message); }
try { JSON.parse('{"a":1 // c\n}'); } catch (e) { console.log(e.message); }
On Node 22 the two messages were Expected double-quoted property name in JSON at position 7 (line 1 column 8) and Expected ',' or '}' after property value in JSON at position 7 (line 1 column 8). Trailing commas and comments are both invalid in strict JSON. Other runtimes word the messages differently, so rely on the position, not the text.
For command-line pipelines, many people use dedicated tools such as yq or jq. We did not run those in this article's environment, so check their documentation for the exact flags in your version.
Performance, readability and tooling considerations
Parsing speed. JSON's grammar is small, and parsers are fast and often built into the language runtime. A full YAML parser has to handle indentation, many scalar styles, anchors and tags, so it is generally more work per byte. For a configuration file read once at startup the difference is irrelevant. For a high-volume API it is not, which is another reason APIs use JSON. Measure before you decide, since results depend on the library.
Payload size. Whitespace and punctuation make JSON slightly larger than the equivalent block-style YAML, and compression erases most of that gap, as we explain in the JSON minification guide. Size is rarely a good reason to pick YAML.
Diffs and review. YAML's line-oriented layout produces clean diffs, because each value sits on its own line. JSON diffs are also clean when it is pretty-printed, but a trailing comma rule means that adding the last element to a list touches two lines. Tools for comparing files and spotting differences help with both.
Readability and accessibility. Plain text with comments is easier to read with screen readers and to navigate with keyboard-only editors than dense bracket-heavy text, but deeply nested YAML can be hard for anyone to follow because the structure is carried by whitespace alone. Keep nesting shallow, prefer meaningful key names, and use editors that draw indentation guides. Provide a schema, so that tooling can offer autocomplete and flag mistakes instead of requiring people to remember the shape.
Schemas. Both formats can be validated with JSON Schema, since YAML data maps onto the JSON data model. A schema catches wrong types, missing keys and misspelled fields, which is the best defence against the typing surprises described earlier.
Editor support. Configure your editor to insert spaces for YAML, show whitespace, and flag duplicate keys. Add a formatter and a linter to your pipeline so that style questions are settled by tools, not by review comments.
Common mistakes and how to avoid them
- Leaving ambiguous strings unquoted. Country codes, versions, zip codes, times and port mappings all need quotes.
- Converting without checking types. After any conversion, inspect values that look like words, numbers or dates.
- Using
yaml.loadon untrusted data. Use the safe loader and validate the result. - Mixing tabs and spaces. Tabs are never allowed for indentation.
- Trusting the editor's alignment. Indentation that looks right may hide a wrong nesting level. Verify the parsed structure.
- Duplicating keys by copy and paste. Neither format's common parsers complain, and the first value is lost.
- Expecting comments to survive tooling. Programmatic edits and conversions drop them.
- Using YAML for data that crosses trust boundaries. Prefer JSON for public input.
- Hand-writing deeply nested JSON configuration. If people must edit it, consider a format with comments, or at least keep the structure shallow and documented.
- Assuming "YAML" means one language. Versions 1.1 and 1.2 differ, and libraries sit between them. Test the actual parser your tools use.
Troubleshooting guide
When a file will not load or loads strangely, work from symptom to cause.
Symptom: "found character '\t' that cannot start any token"
Cause. A tab character was used for indentation.
Fix. Replace tabs with spaces. In most editors, enable "show whitespace" and "convert tabs to spaces", then re-indent the file.
Symptom: "mapping values are not allowed here"
Cause. Either a line is indented more deeply than its siblings, or an unquoted value contains a colon followed by a space.
Fix. Check the reported line and column. Align siblings, or quote the value, for example msg: "hello: world".
Symptom: a value that should be text arrives as a boolean
Cause. An unquoted no, yes, on or off was read by a YAML 1.1 parser. This is the Norway problem.
Fix. Quote the value, or use a library that implements the YAML 1.2 core schema.
Symptom: a version number lost its trailing zero
Cause. 1.10 was parsed as a float.
Fix. Quote it: "1.10". Do the same for every version in a CI matrix.
Symptom: a number is wrong, such as 493 instead of 0755
Cause. A leading zero triggered the YAML 1.1 octal rule, and a value with a colon, such as 1:30, was read as a base-60 number.
Fix. Quote identifiers, zip codes, times and permission strings that must stay text. Use 0o755 only if your parser understands the 1.2 octal form.
Symptom: the second value of a duplicate key is the only one that survives
Cause. The parser keeps the last duplicate silently.
Fix. Add a linter that reports duplicate keys, and review the file for copy-and-paste mistakes.
Symptom: the JSON output is missing my comments
Cause. JSON has no comment syntax, so the converter must discard them.
Fix. Keep the YAML as the source of truth and generate JSON from it. Never edit the generated file.
Symptom: the converted file is huge after expanding anchors
Cause. Every alias becomes a full copy in JSON. A file that reuses a large block many times grows quickly.
Fix. If you need the compactness, keep the YAML as the canonical form and convert at build time. See the JSON minification guide for ways to shrink the output.
Symptom: the converted output has keys in a different order
Cause. The conversion tool sorts keys, as PyYAML's dumper does by default, or a JavaScript object reordered numeric-looking keys.
Fix. Disable sorting where the tool allows it, and do not depend on key order.
Symptom: JSON.parse rejects a file named .json
Cause. The file uses comments or trailing commas, like tsconfig.json, which is JSON with comments, not strict JSON.
Fix. Use a parser that accepts JSONC for such files, or strip the comments before feeding strict parsers.
Best practices checklist
- Pick the format by audience. Human-edited files with comments suit YAML. Machine-exchanged data suits JSON.
- Quote strings that look like other types: booleans, numbers, versions, dates, times, country codes and ports.
- Use two-space indentation and never tabs. Enforce it with an editor config and a linter.
- Always load with the safe loader for any YAML you did not author, and validate the resulting structure with a schema.
- Keep nesting shallow. If indentation goes beyond four or five levels, split the file or restructure the data.
- Use anchors sparingly, and document them. Remember that conversions expand them.
- Treat the YAML as the source of truth when you generate JSON. Do not edit generated files by hand.
- Lint for duplicate keys in CI.
- Pin the YAML version your toolchain assumes, or at least test with the real parser.
- Round-trip test conversions. Convert, convert back, and compare the data.
- Do not put secrets in plain configuration files, in either format. Use a secrets manager or environment-specific injection.
- Use pretty-printed JSON in version control and compact JSON for transport, as described in our guide on JSON formatting.
Comparison: configuration format alternatives
YAML and JSON are not the only options. Here is how they compare with other common choices.
| Format | Comments | Human editing | Strictness | Typical use |
|---|---|---|---|---|
| JSON | No | Fair | Very strict | APIs, manifests, data exchange |
| YAML | Yes | Good, until indentation bites | Loose, version-dependent | Kubernetes, CI, Compose |
| JSONC and JSON5 | Yes | Good | Moderate | Editor and compiler settings |
| TOML | Yes | Very good | Strict, explicit types | Tool configuration files |
| INI | Basic | Good | Loose, no standard | Simple legacy settings |
| XML | Yes | Poor | Strict | Document-centric data, legacy systems |
| Environment variables | No | Good for single values | Strings only | Deployment-time settings |
TOML is worth a look when you want comments and human editing without YAML's implicit typing, since its values have explicit syntax for strings, numbers and dates. XML remains common in older enterprise tooling, and our XML formatting guide explains how to keep it readable. Choose the strictest format that your audience can comfortably edit.
FAQ
Is YAML a superset of JSON?
From YAML 1.2 onward, the specification is designed so that JSON documents are valid YAML. In practice, parser differences and edge cases such as duplicate keys mean you should still test the parser you use.
Why did NO turn into false?
Libraries that follow YAML 1.1 treat unquoted no, yes, on and off as booleans. Quote the value, as in "NO", to keep it a string.
Can YAML contain comments and JSON cannot?
Yes. YAML comments start with #. Strict JSON has no comment syntax, though formats like JSONC and JSON5 add it.
Is YAML safe to parse?
Only if you use a safe loader. Some libraries can construct arbitrary objects from tags in a document, and loading untrusted input with such a loader can run code. Use yaml.safe_load in Python, and check the equivalent in your language.
Do I lose anything when I convert YAML to JSON?
Comments are lost, anchors and merge keys are expanded into copies, multiple documents must be separated, and special types such as dates must become strings.
Can I convert JSON to YAML without losing data?
Yes for plain data. A good converter quotes strings that would otherwise be read as booleans or numbers, so the data round-trips. Always compare the parsed result with the original.
Why are tabs not allowed in YAML?
The specification forbids tabs for indentation because their width varies between editors, which would make the structure ambiguous. Use spaces.
Which is faster to parse, YAML or JSON?
JSON is simpler and parsers are usually faster. For a config file read once, the difference does not matter. For high-volume data, prefer JSON and measure.
Should I use YAML or JSON for configuration?
Use YAML, or another format with comments, when people maintain the file. Use JSON when programs write and read it, or when it crosses a trust boundary.
How do I write a multi-line string in YAML?
Use | to keep line breaks and > to fold lines into one. Add - after either character to remove the final newline.
What is the merge key <<?
It copies the entries of an aliased mapping into the current one, with explicit keys taking priority. It is widely supported but belongs to the YAML 1.1 tradition, so check that your parser supports it.
Why does my version 1.10 become 1.1?
The parser read it as a number. Quote it as "1.10" to keep it a string.
What does tsconfig.json use?
It is JSON with comments and trailing commas, handled by the TypeScript compiler and by editors, not by strict JSON parsers.
Conclusion
YAML and JSON are two answers to different questions. JSON gives you a tiny, strict format that every tool reads the same way, which makes it ideal for data exchange and for anything crossing a trust boundary. YAML gives you comments, readable multi-line strings and reuse, which makes it a pleasure for files people edit, as long as you quote ambiguous values, use spaces, and load documents with a safe loader. When you need to move between them, convert deliberately and check types afterwards. To try it now, paste a snippet into the YAML to JSON tool or the JSON to YAML tool, and keep the YAML cheatsheet open while you work. To keep going, read our guides on JSON formatting and JSON minification and payload size.
Further reading
Try the tools
Transform your JSON data into the human-readable YAML format. This tool is perfect for converting complex JSON objects into a cleaner, more organized structure for configuration and documentation purposes.
Convert your YAML data into the widely-used JSON format with just one click. This tool is essential for developers working with configuration files and data serialization tasks, ensuring compatibility and ease of use.
A helpful cheatsheet for YAML syntax and data types. This is a great resource for developers who work with configuration files and need a quick reference for YAML, ensuring correct and efficient data serialization.


