What each format actually guarantees
Before converting, it pays to know each format’s contract, not the vibe, but what the spec promises. JSON is defined by RFC 8259 and ECMA-404: a tiny syntax, no comments, no trailing comma, and text that, when exchanged between open systems, must be encoded in UTF-8. YAML, in the 1.2.2 spec, is explicitly a superset of JSON, aimed at human readability. TOML, at version 1.0.0 (stable since January 2021), was built for obvious, unambiguous configuration. All three describe the same data; they differ in what they assume on your behalf.
JSON (RFC 8259)
- Strict: mandatory double quotes, no comments, no trailing comma.
- No date type and no infinite-precision integer defined in the spec.
- Ideal for machine-to-machine exchange; noisy to hand-edit.
YAML (1.2.2)
- Superset of JSON, with indentation, comments (#) and anchors.
- Many implicit scalars: where the "Norway problem" is born.
- Clean for humans, sensitive to one extra space.
TOML (1.0.0)
- Explicit types: string, integer, date-time and table with no guessing.
- No significant indentation; sections in brackets.
- Built for project configuration files (Cargo, pyproject).
The data converter works with JSON, YAML, CSV and XML (TOML stays out, but it is a useful reference when you want types with no surprises). CSV and XML add their own trap: CSV has no notion of type, every cell is text, and XML mixes elements with attributes, with no single mapping to an object. The table below sums up what each of the converter’s formats delivers.
| Format | Typed? | Comments? | Specification | Typical use |
|---|---|---|---|---|
| JSON | Yes (limited) | No | RFC 8259 / ECMA-404 | APIs, data exchange |
| YAML | Yes (implicit) | Yes (#) | YAML 1.2.2 | Configuration |
| CSV | No (all text) | No | RFC 4180 (de facto) | Spreadsheets, tables |
| XML | No (text + attributes) | Yes (markup) | XML 1.0 (W3C) | Integrations, documents |
JSON and YAML: superset, not synonyms
The YAML 1.2.2 spec says in its introduction that the main focus of version 1.2 was "making YAML a strict superset of JSON" and that it "removed many of the problematic implicit typing recommendations". In plain terms: every valid JSON is valid YAML, but not every YAML is JSON, and the difference in style is large. JSON marks structure with braces, brackets and quotes, ugly to the eye, impossible to mis-indent. YAML uses space indentation (a tab is a syntax error) and lets you drop quotes, which reads clean in configuration at the cost of ambiguous scalars. The same data in both:
// JSON, estrutura explícita / explicit structure
{ "app": "jkit", "port": 3000, "tags": ["web", "tools"] }
# YAML equivalente / equivalent YAML
app: jkit # comentário permitido / comment allowed
port: 3000
tags:
- web
- toolsThe word that matters in the spec’s sentence is "strict": the compatibility holds for syntax, not for how each loose value is interpreted. And it is precisely in implicit typing, what an unquoted "NO" or "1.10" becomes, that YAML versions diverge. That is why the spec’s date matters as much as its number. Here is how the standards got here:
- 2001JSON is specified
Douglas Crockford describes the format on json.org, extracted from JavaScript’s object syntax.
- 2005YAML 1.1
The schema with broad implicit typing: "yes", "no", "on", "off" and "NO" resolve to boolean. The origin of the Norway problem.
- 2006RFC 4627
The first JSON RFC registers the format as an internet standard.
- 2009YAML 1.2
Becomes a strict superset of JSON and introduces the core schema, which narrows the boolean to true/false.
- 2013ECMA-404 and TOML
JSON’s syntax is standardized as ECMA-404; the same year TOML is created, focused on configuration.
- 2017 and 2021RFC 8259, YAML 1.2.2, TOML 1.0.0
RFC 8259 (STD 90) consolidates JSON; in 2021 the YAML 1.2.2 revision and TOML’s first stable release ship.
The three traps that corrupt in silence
Most conversion bugs are not a visible syntax error, they are a silent re-interpretation of type. The document converts without complaint; it is the data that changed. Three traps cause most of the pain, and all three depend on which parser and which schema you used.
Trap 1, the Norway problem. In YAML 1.1, the unquoted scalars "NO", "no", "on", "off", "yes", "y" and "n" are booleans. Norway’s country code is NO; unquoted, it resolves to false. PyYAML follows the 1.1 schema by default, so this is the real behavior of millions of files. Run the YAML below through a 1.1 parser and watch the country vanish:
# Entrada YAML (interpretada com o schema 1.1, ex.: PyYAML)
# YAML input (interpreted with the 1.1 schema, e.g. PyYAML)
country: NO
shipping:
express: yes
weekend: off
// Saída JSON, os escalares viram booleanos
// JSON output, the scalars become booleans
{
"country": false,
"shipping": { "express": true, "weekend": false }
}YAML 1.2 fixed this: the core schema resolves as boolean only true, True, TRUE, false, False and FALSE. Always confirm which schema your parser uses. This page’s data converter, for instance, runs on the js-yaml library, which in the installed version uses the 1.2 core schema by default, so "country: NO" arrives as the string "NO", not as false. The universal defense, whatever the parser, is one: quote any code, acronym or value that could collide with a boolean.
Trap 2, integer precision. The JSON spec sets no limit on a number’s size, but JavaScript represents every number as a 64-bit floating point (IEEE 754 binary64). The largest integer that fits without loss is Number.MAX_SAFE_INTEGER, defined in the ECMAScript specification. Above it, integers jump by twos, then by fours, and the arithmetic silently rounds.
Number.MAX_SAFE_INTEGER = 2^53 - 1 = 9007199254740991- 2^53
- a float64 mantissa has 52 explicit bits + 1 implicit, giving 53 bits of integer precision
- - 1
- the largest integer representable without gaps is 2^53 minus one
// Entrada JSON, dois ids acima do limite seguro
// JSON input, two ids above the safe limit
{ "orderId": 9007199254740993, "userId": 1541815603606036480 }
// Depois de JSON.parse e serializar de novo (JavaScript)
// After JSON.parse and re-serializing (JavaScript)
{ "orderId": 9007199254740992, "userId": 1541815603606036500 }Trap 3, dates. JSON has no date type: a date is always a string, and it is on you to agree on the format (ISO 8601 is the de facto one). YAML 1.1, by contrast, has an implicit timestamp, an unquoted "2026-05-05" is converted to a date object by the parser, changing the value’s type without you asking. Going from YAML to JSON, that object becomes a date string that may not match the format the other side expects. If your field is a version ("2026.05"), a code or a date that needs a timezone, quote it, and when the timezone enters the math, the time zones guide shows why UTC and daylight saving turn a seemingly harmless string into a bug.
Duplicate keys, anchors and the YAML bomb
Beyond the three type traps, a second group of risks lives in the structure and in the parser’s security. Repeated keys, anchors that multiply and an unsafe loader can, respectively, lose data, crash the server and execute code. Each one folds out of the main flow below, open the one you need.
Duplicate keys: allowed by syntax, undefined in effect
RFC 8259 (§4) says "the names within an object SHOULD be unique" and, if they are not, "the behavior of software that receives such an object is unpredictable". Some libraries keep the last pair, some the first, some collect them all, some raise an error. In other words: a JSON with the same key twice is syntactically valid, but which value survives depends on the parser.
YAML is stricter: the spec forbids duplicate mapping keys. Even so, not every parser complains, several just keep the last one. The defense is to validate uniqueness before converting, especially when two config files are merged and a key shows up in both.
Anchors and aliases: reuse with & and *
YAML lets you name a node with an anchor (&name) and reuse it later with an alias (*name), avoiding repetition in large files. It is handy, but it has two consequences: converting to JSON expands the aliases, the reused structure is copied at each point, and the file can balloon. And, like any alias-pointed value, the expansion happens at parse time, not at read time.
Billion laughs: the YAML bomb
Alias reuse becomes a weapon when nested. You define an anchor with a list of ten items, a second anchor with ten copies of the first, a third with ten copies of the second, and so on. Each level multiplies the size by ten; a few lines of text expand to billions of nodes in memory. This is the billion laughs attack (or YAML bomb), a denial of service: the parser tries to materialize the structure and the process is killed for running out of memory.
The mitigation is to cap the expansion: modern parsers count alias resolutions and stop at a ceiling (the npm yaml package limits to 100 by default; SnakeYAML to 128). When accepting YAML from an untrusted source, use a parser with that limit or reject aliases outright.
Why JSON has no comments
The RFC 8259 grammar allows no comment: the only insignificant whitespace is space, tab, line feed and carriage return. A // or /* */ invalidates the whole document. It was a deliberate choice by Douglas Crockford, comments were being stripped by some generators to smuggle in parsing directives, which broke interoperability. If you need a note in a JSON, use a data field (for example "_comment"); if you need a real comment, that is a good sign the right format is YAML or TOML.
Converting in practice, without corrupting
Here is a real CSV-to-JSON conversion. The first CSV line becomes the keys; each following line becomes an object. Since CSV keeps no type, every value arrives as a string, numbers and booleans included, and an empty field becomes "". It is the most obvious case of type loss, and the one that most surprises people who expect 42 to stay a number:
name,age,active
Pedro,42,true
Ana,,false
// vira / becomes:
[
{ "name": "Pedro", "age": "42", "active": "true" },
{ "name": "Ana", "age": "", "active": "false" }
]In the other direction, JSON to CSV, nested structures like a list of roles are serialized as text inside the cell, because a flat table cannot represent hierarchy. Put that together with what we have seen and the risk map looks like this:
| Trap | Who suffers | How to avoid |
|---|---|---|
| Accidental boolean (Norway) | Parsers on the YAML 1.1 schema (PyYAML) | Quote the value; use a 1.2 parser |
| Integer precision loss | JSON.parse and any JavaScript runtime | Keep large ids as strings |
| Self-converted date | YAML 1.1 implicit timestamp | Quote it; agree on ISO 8601 |
| Erased type | CSV (everything becomes text) | Coerce the type after converting |
| Duplicate key | JSON (undefined) and lax YAML parsers | Validate uniqueness before merging |
| Code execution / bomb | Unsafe yaml.load; parser with no alias cap | safe_load and an expansion cap |
The best way to internalize all of this is to try it. Paste a YAML with an unquoted "NO", a giant id or a loose date into the converter below and watch what comes out the other side, all in the browser, sending nothing to a server:
Locate, convert and validate the type
Often you do not want the whole document, just one value deep inside. A path is that value’s address: starting from the root $, you descend through keys and indexes, $.user.roles[0] grabs the user’s first role. The JSON path finder returns the path of any node you click, which helps you inspect exactly the field you will convert before touching the whole file. Extracting values by a text pattern is the other half of the job, covered by the regex from scratch guide.
- Confirm which schema your YAML parser uses (1.1 vs 1.2) before trusting any unquoted scalar.
- Quote codes, acronyms, versions and dates that could collide with a boolean, number or timestamp.
- Keep ids and monetary values above 2^53 − 1 as strings, from the database to JSON and back.
- Validate key uniqueness when merging config files.
- Use safe_load (or equivalent) for YAML from an untrusted source, with an alias-expansion cap.
- After structuring the data, format the rest of the pipeline, make the query readable in the SQL formatter.
The pattern that solves almost everything is the same in any conversion: locate the value with a path, convert with a parser whose schema you know, and validate the type on the other side. Conversion is never the problem; the assumption that the type survived intact is. If your ids follow a scheme other than integers, the UUID and ULID guide shows alternatives that never come near the float64 limit.
Frequently asked questions
Why does "NO" become false when I convert YAML?
Why did my large id change value after converting?
Does YAML allow comments and JSON does not?
Is a duplicate key in JSON an error?
Is PyYAML’s yaml.load safe?
Which format for configuration: JSON, YAML or TOML?
JSON and YAML describe the same data with different philosophies, and conversion is safe only when you respect what each one assumes. The three silent traps are the Norway problem (a "NO" that turns into false in the YAML 1.1 schema), integer precision loss above 2^53 − 1 = 9,007,199,254,740,991, and dates that YAML 1.1 converts on its own. Add duplicate keys, anchors that blow up into billion laughs, and the unsafe yaml.load. The defense fits in one sentence: quote ambiguous scalars, keep large ids as strings, check your parser’s schema, and validate the type on both sides.
Sources & references
- RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format
- ECMA-404, The JSON Data Interchange Syntax
- YAML 1.2.2, Official specification (includes the core schema, §10.3)
- ECMAScript (ECMA-262), Number.MAX_SAFE_INTEGER
- PyYAML, official guidance on yaml.load and safe_load
- TOML v1.0.0, Specification
- js-yaml, YAML parser used by the converter (default schema)