Skip to main content

Converting JSON to TOML: Tables, Key-Values, and Cleaner Config

A practical guide to converting JSON to TOML by hand and with a tool: how nested objects become tables, how arrays of tables work, and when TOML beats YAML or JSON for config.

Published By Li Lei
#json #toml #config #converter #developer-tools

Converting JSON to TOML: Tables, Key-Values, and Cleaner Config

JSON is the lingua franca of APIs and machine-to-machine data, but it is a clumsy thing to hand-edit. Every value sits inside braces, every line ends in a comma you have to remember, and a config file three levels deep starts to look like a punctuation accident. TOML was designed for exactly that pain point: a config format meant to be read and edited by people, while still mapping cleanly onto a hash table underneath.

If you already have your data shaped as JSON and you need it as a Cargo.toml or a pyproject.toml, you do not have to retype it. This post walks through what TOML actually is, how each JSON construct maps to a TOML one, and where TOML earns its place over YAML or raw JSON for configuration. There is a worked example near the end you can paste straight into the JSON to TOML converter to follow along.

What TOML Is and Where You Meet It

TOML stands for Tom's Obvious Minimal Language. The name is a fair description of the goal: minimal syntax, obvious meaning. A scalar assignment is just name = "toolora". A section is introduced with a bracketed header like [database]. A repeated section uses double brackets, [[servers]]. That is most of the grammar.

You probably already edit TOML even if you have never thought about the format by name:

  • Rust uses Cargo.toml for package metadata and dependency pins.
  • Python has consolidated tool configuration into pyproject.toml, which now holds build settings, dependencies, and config for tools like Ruff and pytest.
  • Static site generators such as Hugo, and hosts like Netlify, read TOML site config.
  • Countless CLI tools store their settings in a .toml file in your home directory.

The reason TOML keeps showing up is that it hits a sweet spot. It is strict enough that a parser maps it unambiguously to a dictionary, and loose enough on syntax that a human can scan it top to bottom without counting brackets.

How Nested Objects Become Tables

This is the conversion that trips people up the most, so it is worth being concrete. A nested JSON object does not become a nested set of braces in TOML. It becomes a table with a dotted header.

Take the small input {"owner": {"name": "Li"}}. The TOML form is:

[owner]
name = "Li"

The object key owner is promoted to a section header, and its contents become key-value lines underneath. Go one level deeper with {"a": {"b": {"c": 1}}} and you get a chain of headers:

[a]
[a.b]
c = 1

There is an ordering rule that makes this work and that you should keep in your head when you write TOML by hand: at any given level, scalar key-values must come before the sub-tables. A table header silently ends the body of the previous table, so if you write a bare key = value after a [section] header, that key belongs to the section, not the parent. Putting scalars first keeps the meaning unambiguous. A converter handles this automatically, but it is the single most common reason hand-written TOML parses into the wrong shape.

Arrays: Inline Lists Versus Arrays of Tables

JSON has one array syntax. TOML has two, and picking the right one is what makes generated TOML look hand-written instead of machine-dumped.

When the array holds simple scalars, it stays inline:

ports = [8001, 8002, 8003]

Short, flat lists read better on one line, so a number or string array maps directly to a bracketed inline array.

When every element of the array is an object, TOML uses an array of tables, written with double brackets. The input {"servers": [{"host": "a"}, {"host": "b"}]} becomes two distinct blocks:

[[servers]]
host = "a"

[[servers]]
host = "b"

Each [[servers]] header opens a new entry in the servers array. This is the construct that looks cryptic in the spec but obvious once you see it next to the JSON it came from. If you are still building the intuition, paste a shape you understand into the converter and read the two side by side; it lands faster than the prose ever does.

A Worked Example

Here is a realistic chunk of project config as JSON. Imagine it came out of an API or a settings export:

{
  "package": {
    "name": "toolora-cli",
    "version": "1.4.0",
    "edition": "2021"
  },
  "dependencies": {
    "serde": "1.0",
    "clap": "4.5"
  },
  "ports": [8001, 8002],
  "servers": [
    { "host": "a.internal", "weight": 5 },
    { "host": "b.internal", "weight": 3 }
  ],
  "cache": null
}

Converted to TOML, that becomes:

ports = [8001, 8002]

[package]
name = "toolora-cli"
version = "1.4.0"
edition = "2021"

[dependencies]
serde = "1.0"
clap = "4.5"

[[servers]]
host = "a.internal"
weight = 5

[[servers]]
host = "b.internal"
weight = 3

Three things are worth pointing out. The package and dependencies objects became [package] and [dependencies] tables, which is exactly the structure a real Cargo.toml uses. The servers array of objects became two [[servers]] blocks. And cache disappeared entirely, because its value was null.

That last point matters: TOML has no null literal. Writing cache = null would produce a file no parser accepts, so a null-valued key is dropped rather than written out as broken syntax. If you need to record that a key was present, swap the null for an empty string "" or a sentinel like "none" in your JSON before converting.

TOML Versus YAML Versus JSON for Config

I have shipped config in all three formats, and my honest take is that the choice is about who edits the file and how irregular the data is.

JSON is the right call when the file is generated and consumed by machines and never touched by a human. It supports null, it is universally parseable, and its strictness is a feature when no person is in the loop. The moment you ask someone to hand-edit it, though, the braces and trailing-comma rules start costing you. When I just need to tidy a JSON blob before reading it, I reach for a JSON formatter rather than fighting the indentation by hand.

YAML goes the other direction. It is the most forgiving to write for deeply nested or irregular trees, which is why Kubernetes and CI pipelines live in it. The cost is that YAML's significant whitespace and its surprising type coercions (the famous case where no parses as a boolean false) make it easy to introduce a bug that no bracket would have let through.

TOML sits between them. It is flatter than YAML, with explicit [section] headers instead of indentation you have to count, and it has first-class dates, which JSON lacks entirely. The trade-off is real: TOML has no null, and it gets awkward for deeply irregular nested structures where the array-of-tables model fights the data. For the common shape of config, a handful of named sections with key-value settings under each, TOML is the most pleasant of the three to read and edit.

Working in Your Browser, Privately

The conversion described here is pure JavaScript that runs in your browser tab. Your JSON, the parsed data, and the TOML output never leave the page and nothing is logged. The one caveat worth repeating: the shareable link encodes your input in the URL query string, so a link pasted into a chat will record that input in the recipient server's access log. For secrets or private config, use the copy button and paste the text rather than sharing the URL.

If TOML is not your target and you are headed somewhere else, the same browser-side approach covers neighboring formats too, including JSON to YAML when you need indentation-style config instead of bracketed tables. Start with whichever your runtime actually reads, keep the source in JSON where your editor gives you validation, and let the converter do the deterministic mapping.


Made by Toolora · Updated 2026-06-13