DevKitHub

JSON & Data

TOML to JSON Converter (and JSON to TOML)

Paste a TOML file, such as Cargo.toml or pyproject.toml, to get JSON, or switch direction to turn JSON into TOML. Errors name the exact line and column, and the first definition of anything defined twice.

18 lines
36 lines

TOML is read by the 1.0.0 rules, the version Python’s tomllib implements. JSON has no dates and no null, so dates become strings one way and null keys are left out the other, each with a warning.

This tool runs entirely in your browser. Your input is never uploaded, stored or logged.

How it works

The TOML is parsed by the 1.0.0 grammar, character by character, and the part most parsers are lenient about is how tables come into existence. A table can be defined by a [header], by dotted keys such as apple.color = "red", or implicitly as the parent of a deeper header, and each table may be defined only once. So [package] twice is an error; so is a [fruit.apple] header after apple.color was set under [fruit]; so are dotted keys that reach into a table another header defined; and an inline table { } or an array written as a value is complete as written, so no later header can add to it. Each of these errors names the line of the first definition. The parser is checked against the TOML 1.0 cases of toml-test, the language-independent TOML test suite: it reads all 208 valid documents to the expected values and rejects every invalid one that can be pasted as text.

Most values map directly to JSON, which lacks three things TOML has. It has no date type, so the four TOML date and time types become RFC 3339 strings, 1979-05-27 07:32:00Z becoming "1979-05-27T07:32:00Z", and converting back gives a string, not a date. It has no infinity or NaN, so inf and nan become null. And JavaScript reads JSON numbers as 64-bit floats, so an integer beyond 2^53, which a TOML integer can be, is written with every digit but flagged, because JSON.parse would round it. Each of the three comes with a warning naming the keys. Hexadecimal, octal and binary integers are written in decimal, so 0o755 becomes 493; floats keep their decimal point, so 1.0 does not turn into the integer 1; and keys stay in the order they were written.

Going the other way, an object becomes a [table] and an array of objects becomes an array of tables, one [[name]] section per element. Inside each table the plain values come first and the sub-tables after them, because in TOML every key below a [header] belongs to that header, so keys can come out in a different order from the JSON. Keys are quoted only when they hold something other than letters, digits, - and _. A string with line breaks becomes a multi-line string, a string with backslashes, such as a Windows path or a regular expression, is put in single quotes where that avoids escaping, and an array too long for 80 columns gets one element per line. TOML has no null: a null member is left out, with a warning, and a null inside an array is an error, because leaving it out would move every element after it. TOML 1.1 syntax, such as line breaks inside inline tables, \e and \x escapes, and times without seconds, is refused with a message that says so.

Common problems

Every example below is run against this tool in our test suite, so what it says here is what the tool actually does.

\U must be followed by eight hexadecimal digits.

data_dir = "C:\Users\ada\data"
Why:
A Windows path in double quotes. Inside "..." every backslash starts an escape, so \U reads as the start of a Unicode escape and the path is not a path at all. With \n or \t in the path it would even parse, and quietly contain a line break or a tab.
Fix:
Put the path in single quotes, which have no escapes: data_dir = 'C:\Users\ada\data'. Or double every backslash.

The table [package] is already defined on line 1.

[package]
name = "web"

[dependencies]
serde = "1.0"

[package]
version = "0.2.0"
Why:
The same [header] twice, usually after a paste or a merge. TOML allows a table to be defined only once, however far apart the two headers are.
Fix:
Move the keys under the first [package] and delete the second header.

The table [tool.poetry] was already defined by dotted keys on line 2.

[tool]
poetry.name = "web"

[tool.poetry]
version = "0.1.0"
Why:
A dotted key such as poetry.name = ... defines the table tool.poetry, and a later [tool.poetry] header tries to define it a second time. The TOML spec gives the same case, with [fruit] and apple.color, as an example of invalid TOML.
Fix:
Use one form: either every key as poetry.something under [tool], or all of them under [tool.poetry].

Strings must be in quotes in TOML: write key = "web".

name = web
Why:
An unquoted string, a habit from YAML and .ini files. In TOML only numbers, booleans, dates, arrays and inline tables are written bare; true, 42 and 1979-05-27 are values, and web is an error.
Fix:
Quote it: name = "web".

An inline table { } must be on one line in TOML 1.0.

serde = { version = "1.0",
  features = ["derive"] }
Why:
An inline table split over two lines. TOML 1.0 forbids line breaks inside { } (TOML 1.1 allows them), so a parser built on 1.0, such as Python’s tomllib, refuses the file.
Fix:
Keep the inline table on one line, or give it a section of its own: [dependencies.serde] with version and features under it.

A date in the TOML came back from the JSON as a plain string.

Why:
JSON has no date type, so TOML dates and times are written as RFC 3339 strings. Converting that JSON back to TOML cannot tell a date from a string that looks like one, so it writes a string.
Fix:
Treat those fields as dates in the code that reads the JSON, or edit the quotes off the values in the TOML after converting back.

Frequently asked questions

Does it validate pyproject.toml or Cargo.toml?
It checks that the file is valid TOML 1.0, which is the first thing pip, Poetry or Cargo does with it, and points at the line and column of any error. It does not check the contents against a schema: a misspelt field in [project], or a dependency with an impossible version, is still valid TOML and passes.
Why did my dates become strings?
Because JSON has no date type. TOML’s offset date-times, local date-times, dates and times are written as RFC 3339 strings, with the date and time joined by T, and a warning lists them. Converting back to TOML gives strings too, since nothing in the JSON says they were dates.
What happens to null when converting JSON to TOML?
TOML has no null, and a missing key is how a TOML file says "no value", so a null member is left out and a warning names it. A null inside an array is an error instead, because dropping it would change the position of every element after it.
Which version of TOML does it read?
TOML 1.0.0, the version Python’s tomllib implements. TOML 1.1 allows line breaks and a trailing comma in inline tables, the \e and \xHH escapes, and times without seconds; here those are errors, each with a message saying the syntax is from 1.1, so a file that passes will load in a 1.0 parser.
Why are the keys in a different order in the TOML?
Because every key written after a [header] belongs to that table, so a table’s own values have to come before its sub-tables. An object that lists a nested object before a plain value has the plain value moved up. The data is the same; JSON and TOML both treat key order as insignificant.

Last updated