JSON, TOML, YAML, or CSON: Choosing a Configuration Format
Bottom line: Config files need comments, dates and multi-line text, which is where JSON falls short. Compare JSON, TOML, YAML and CSON, and see what conversion always loses.

Bottom line: use JSON for machine exchange, and choose TOML or YAML for human-maintained configuration according to the ecosystem (YAML for Kubernetes and CI, TOML for flat structures or native dates); fall back to JSONC only when you are unsure whether the reader supports the dialect.
JSON exists for machines exchanging data; a configuration file is written by people for people. Mix the two up and you get a
config.jsonthat cannot hold a comment and that nobody dares to trim.
1. Start with two different kinds of files
Data interchange and configuration do not share the same goal, even though the same person often writes both on the same machine.
| Dimension | Data interchange | Configuration |
|---|---|---|
| Who writes it | A program serialising a value | A human, or a human editing generated output |
| Who reads it | Someone else's program | Your own program |
| What matters | Zero ambiguity, size, parse speed | Readability, comments, reviewable diffs |
| Change frequency | Every request | Occasionally, with review |
| Cost of a mistake | A 500 response | A service that will not start |
JSON scores almost perfectly in the left column: minimal grammar, a parser in every language, and no comments means no "comment drifted away from the value" problem. In the right column, its strictness is pure friction: no comments, no trailing commas, no dates, and multi-line text has to be collapsed into one line with \n.
2. The four contenders and their syntax
- JSON: defined by RFC 8259 and ECMA-404, with exactly six value types: object, array, string, number, boolean, and
null. It is the de facto interchange standard and the only format that needs no selection decision. - TOML: created by Tom Preston-Werner, with v1.0.0 published in January 2021. Its stated goal is a minimal configuration format with obvious semantics that maps unambiguously to a hash table.
Cargo.tomlandpyproject.tomluse it by default. - YAML: the current specification is 1.2.2. Indentation expresses hierarchy, and it supports comments, anchors, and multiple documents. Kubernetes manifests, GitHub Actions, and Ansible all depend on it, at the cost of a large specification and implementation differences.
- CSON: CoffeeScript Object Notation, which writes configuration using CoffeeScript object literals. Atom's
config.csonused it. It supports comments, unquoted keys, and trailing commas, but its ecosystem never left the JavaScript world.
2.1 Capability comparison
| Capability | JSON | TOML | YAML | CSON |
|---|---|---|---|---|
| Comments | ✗ | ✓ # |
✓ # |
✓ # |
| Trailing commas | ✗ | ✓ (arrays, inline tables) | ✓ (flow collections) | ✓ |
| Unquoted keys | ✗ | ✓ | ✓ | ✓ |
| Native date-time | ✗ (string only) | ✓ four date-time types | ✓ (auto-parsed in 1.1) | ✗ |
| Multi-line strings | ✗ (\n escapes) |
✓ """ and ''' |
✓ block and folded scalars | ✓ heredoc |
| Integer range | Unbounded in spec, double in practice | Signed 64-bit, must be lossless | Arbitrary precision plus .inf/.nan |
Follows JavaScript |
| Nesting | Braces and brackets | Tables, arrays of tables, dotted keys | Indentation | Indentation |
| Indentation-sensitive | ✗ | ✗ | ✓ | ✓ |
null |
✓ | ✗ | ✓ | ✓ |
| Tool coverage | Every language | Mainstream languages | Every language, varying implementations | JavaScript only |
Two cells deserve a note. TOML has no null, which is the most common failure when converting from JSON. YAML's "native date" behaviour means a 1.1 parser turns 2026-10-31 into a date object automatically, so the type drifts across languages.
3. Comments: the one JSON weakness
The conclusion first: if a human maintains the file and needs to explain why a value is what it is, do not use standard JSON.
There are four practical routes, ordered by how invasive they are:
- Switch to JSONC, which is standard JSON plus comments and trailing commas. VS Code's
settings.jsonandtsconfig.jsonuse it. The cost is that the reader must use a comment-aware parser such asjsonc-parser; plainJSON.parsefails. Details are in JSON5 and JSONC: JSON with comments. - Keep a sidecar document.
config.jsonstays machine-readable, whileconfig.mdexplains what each field means and why it holds that value. Both live in the same directory and change in the same commit. - Put explanations in the schema. JSON Schema's
descriptionand$commentkeywords can document fields, but schemas rarely ship alongside runtime configuration, so the person reading the config may never see them. - Move to TOML or YAML, letting the format itself provide comments that every parser understands.
{
// Fixed at 8080: the frontend build script reads this port for its proxy
"port": 8080,
// Disable watching to avoid exhausting inodes in CI
"watch": false
}
JSONC is still a dialect: the extension stays .json, but only some tools understand it. When data leaves the team or crosses a service boundary, the line between JSONC and standard JSON has to be documented.
4. Dates and multi-line strings: TOML's home turf
TOML promotes date-time to a first-class type with four variants: offset date-time, local date-time, local date, and local time. Multi-line strings have dedicated syntax that preserves line breaks:
title = "json-toolkit"
created = 2026-10-31T09:00:00+08:00
description = """
A JSON toolkit for developers.
Every operation runs locally in the browser.
"""
The same information in JSON has to demote the date to a string and escape the line breaks:
{
"title": "json-toolkit",
"created": "2026-10-31T09:00:00+08:00",
"description": "A JSON toolkit for developers.\nEvery operation runs locally in the browser."
}
In JSON, "created" is just a string that happens to look like a date, so the type information has to live in a field contract: both sides must agree that the field is always RFC 3339. TOML gives the parser an actual date object with no verbal agreement required.
YAML can also hold multi-line text, but the two block styles mean different things: | preserves line breaks while > folds them into spaces. Choose the wrong one and an address or a SQL statement collapses into a single line, an error that is very hard to catch in review.
5. Numbers: the most overlooked difference
The JSON specification puts no limit on numeric precision, but RFC 8259 also warns that interoperability is safest when numbers are treated as IEEE 754 doubles. In JavaScript, integers above 2^53 - 1 silently lose precision:
JSON.parse('{"id":9007199254740993}').id
// 9007199254740992: the last digit is gone
TOML requires signed 64-bit integers to be handled losslessly and to raise an error rather than truncate silently; floats follow binary64 and may also be inf or nan. YAML 1.1 carries an older burden: 1:30 is parsed as 90 (sexagesimal), and booleans accept yes, no, on, and off. Hence the classic "Norway problem", where the country code NO becomes false.
For money, never rely on floating point in any of these formats: use integer minor units, strings, or a database decimal type. Formatting and precision are covered in more depth in the boundaries of JSON, YAML, and XML conversion.
6. Tables and nesting: TOML's hard boundaries
TOML expresses nesting with tables and arrays of tables:
[server]
port = 8080
[[routes]]
path = "/parse"
method = "POST"
[[routes]]
path = "/minify"
method = "POST"
It has two hard boundaries you must know before converting:
- A TOML document's root is a table, so a JSON document whose top level is an array (such as
[1, 2, 3]) has no legal TOML representation; it must be wrapped as{"items": [...]}; - TOML has no
null, so a JSONnullfield must either be dropped or replaced with an empty string or a sentinel value, with the semantics pushed onto the consumer.
YAML is the opposite: nesting depth is unlimited, and anchors plus merge keys can reuse fragments:
defaults: &defaults
retries: 3
timeoutMs: 5000
server:
<<: *defaults
port: 8080
Anchors are a YAML-only capability and a guaranteed loss during conversion: JSON has no reference syntax, so the expanded file grows and nothing records that the two sections used to be the same configuration.
7. Choosing by scenario
| Scenario | Recommendation | Reason |
|---|---|---|
| Public APIs, message queues, caches | JSON | Maximum universality, zero negotiation cost |
| Application config, flat and typed | TOML | Comments, dates, lossless integers, stable parser behaviour |
| Kubernetes, CI, Ansible | YAML | The ecosystem is locked in; changing format means changing tools |
VS Code, tsconfig.json |
JSONC | Natively supported, no new format to introduce |
| Reusing configuration fragments | YAML | Anchors and merge keys are the only ready-made option |
| Generated and machine-only | JSON (minified) | No comments needed, best size and parse cost |
| Legacy Atom or CoffeeScript projects | CSON, but plan a migration | The ecosystem is marginal and migration costs grow with time |
One rule of thumb: let the ecosystem choose the configuration format, not personal preference. Writing Rust means Cargo.toml, deploying to Kubernetes means YAML, and building a VS Code extension means JSONC. Only in a new project with no ecosystem constraints is "which one is nicer to write" a real question.
8. Conversion boundaries
| Direction | Information that is always lost | Advice |
|---|---|---|
| JSON → TOML | null, top-level arrays, keys that cannot be expressed |
Normalise the structure and drop empty fields first |
| TOML → JSON | Comments, date types, inf / nan |
Convert dates to RFC 3339 strings |
| YAML → JSON | Comments, anchors and merge keys, tags, multiple documents | Expand anchors first, then convert document by document |
| JSON → YAML | No structural loss, but date strings may be auto-parsed | Quote date strings |
| CSON → JSON | Comments, CoffeeScript expressions | Migrate once; do not sync in both directions |
Conversion tools guarantee the values, never the comments. So do not design a two-way pipeline where "JSON is the source and YAML is a build artifact". Either generate in one direction only, or maintain a single source file.
9. Checklist
- [ ] Document which file types use which format, and list the dialects that are allowed;
- [ ] Prefer TOML or YAML for human-maintained configuration, and JSON for machine exchange;
- [ ] Store dates in JSON as RFC 3339 strings and mark the format in the schema;
- [ ] Represent money and integers above
2^53as strings or minor units, never as floats; - [ ] Validate each format in CI with its own parser, never with
JSON.parsefor YAML; - [ ] Migrate formats in one direction only, and delete the old file so the two cannot drift.
FAQ
Why does converting JSON to TOML fail on some fields?
TOML has no null, and a TOML document's root is a table, so a JSON null field must be dropped or replaced with an empty string or sentinel, and a top-level array must be wrapped as {"items": [...]}. Comments, date types, and inf/nan are also lost when moving in the opposite direction, which is why conversion should be one-way.
Should I keep two configuration files in different formats?
No. Conversion tools guarantee the values but never the comments, so a pipeline where "JSON is the source and YAML is a build artifact" will drift. Either generate in one direction only, or maintain a single source file and delete the old one after migrating.
How do I add comments to a JSON configuration file?
Standard JSON has no comments. You can switch to JSONC (the VS Code dialect, read with jsonc-parser), keep a sidecar document that explains each field, or move to TOML or YAML. Files sent to other systems should stay standard JSON, with explanations living in documentation or a schema.
Is TOML or YAML better for application configuration?
Choose TOML when the structure is flat, mostly key-value pairs, and native dates matter; its semantics are more predictable and parser differences are smaller. Choose YAML when you need deep nesting, multiple documents, anchor reuse, or when the target ecosystem is YAML-first, such as Kubernetes or CI. Both support comments, so the real differentiator is structural complexity and ecosystem lock-in.
Why does NO turn into false in YAML?
YAML 1.1 treats yes, no, on, off, y, and n as booleans, so Norway's country code NO is parsed as false. This is the "Norway problem". Avoid it by using an implementation that follows YAML 1.2, or by quoting every string that could be misinterpreted.
Is CSON still worth using?
Not for new projects. CSON depends on the CoffeeScript ecosystem, has few maintained tools, and has limited editor and CI support; after Atom stopped being maintained its use cases shrank further. If a project already has CSON configuration, schedule a one-time migration to JSON, TOML, or YAML rather than adding more of it.