In the world of software development, configuration files are a critical component. They store settings and metadata for applications, enabling users and developers to customize functionality without diving into the source code. Over the years, various formats have been used for configuration files, such as JSON, YAML, and XML. While all of them have their strengths, each has its quirks that may lead to complexity or errors.
TOML (Tom’s Obvious, Minimal Language), a simple and human-readable configuration file format designed to address these challenges. In this blog, we’ll explore TOML’s features, syntax, and use cases. Along the way, we’ll break down some code examples and provide detailed explanations to ensure clarity.
What is TOML?
TOML is a data serialization language primarily used for configuration files. It was created by Tom Preston-Werner, co-founder of GitHub. It’s created with the goal of being minimal, meaning it doesn’t have unnecessary complexity or confusing syntax. The key design goals of TOML are:
- Readability: TOML uses a simple, intuitive syntax that’s easy for humans to read and write. It mirrors common formats like INI but offers more structure..
- Minimalism: TOML keeps the syntax simple while supporting complex data types.
- Interoperability: It is easy to parse into various programming languages.
TOML files typically have a .toml extension and are widely used in projects like Rust’s Cargo and Python’s Poetry. Nowadays, it is also used in Android projects for Gradle version catalogs.
Minimalist and Easy-to-Read Format
TOML stands for Tom’s Obvious, Minimal Language, which gives you an idea of its core principle: keep things simple and clear. It was designed to minimize unnecessary syntax, which means it won’t overwhelm you with complex rules. Each piece of data is laid out clearly so that it’s easy for anyone to read and understand.
What makes TOML files so easy to read? One of its key features is its obvious semantics. When you look at a TOML file, it’s easy to figure out what each part means without much effort. Take this simple example.
host = "localhost"
port = 8080
Here, host
is the key, and "localhost"
is its value. Similarly, port
is the key, and 8080
is the value. There’s no ambiguity — just a straightforward, easily interpretable configuration. Even if you’re not a developer, the structure is clear enough to understand what the data represents.
Clear Mapping to Hash Tables
One of the major design goals of TOML is that it maps naturally to a hash table. In programming, a hash table is a data structure that stores data in key-value pairs. This structure makes it easy to retrieve values associated with specific keys.
In TOML, each line is essentially a key-value pair, which makes it ideal for converting into a hash table. When a TOML file is read by a program, the key-value pairs are mapped into a data structure like a dictionary or map. For example:
host = "localhost"
port = 8080
This could be translated into a dictionary like this in Python:
config = {"host": "localhost", "port": 8080}
This mapping allows for easy access to the data programmatically — you can quickly retrieve the value for a key (like host
) without any confusion or overhead.
Cross-Language Parsing
TOML’s simplicity doesn’t just make it easy to read, it also ensures that parsing the file into usable data structures is straightforward in a wide variety of programming languages. TOML is designed to be compatible with many languages, including Python, Go, JavaScript, Rust, and more.
When you parse a TOML file in a program, it’s typically converted into a data structure like a map, dictionary, or object. This makes it easy to work with, regardless of the programming language. For example, in Python, a TOML file can be parsed into a dictionary, while in Go, it can be converted into a map.
This wide support means that TOML can be used in almost any environment(even in Kotlin also, I’ll get to that point later. First, let’s cover the basics), making it a versatile choice for managing configuration data.
Syntax Overview
TOML’s syntax is clean and minimal. Let’s explore its key components:
Key-Value Pairs
The core of TOML is the key-value pair. Keys represent names, and values store the associated data.
key = "value"
# multiple key/value pairs are
key1 = "value1"
key2 = "value2"
key3 = "value3"
# INVALID Syntax
key =
# invalid (because value is unspecified)
first = "amol" last = "pawar"
# invalid (because multiple key/value pairs on same line.
# Please note, to make any line a comment, simply mark it with a hash symbol at the beginning.
Please note that:
- To make any line a comment, simply mark it with a hash symbol at the beginning.
- Key name, equal sign and value should be on same line, and whitespaces around key names and values should be ignored.
- For values, we can use types such as String, Integer, Float, Boolean, Date, Array, and inline tables.
- We can’t place multiple key/value pairs on the same line; otherwise, they will be considered invalid.
- Defining the same key multiple times is also invalid.
- If a value is unspecified, it will be considered invalid.
Types of Keys
There are three types of keys: bare, quoted, and dotted.
Bare keys
When you’re using bare keys (keys without quotes) in a file like TOML (a configuration file format), there are specific rules for naming them:
1. You can only use:
- English letters (A-Z, a-z)
- Numbers (0–9)
- Underscores (_)
- Dashes (-)
2. Bare keys can be made up of just numbers (like 1234
), but even in that case, they will be treated as text (a string), not a number.
3. A bare key (a key without quotes) must not be empty — it has to contain at least one letter, number, underscore, or dash.
# valid bare keys
key = "value"
bare_key = "value" # Valid, uses letters and underscore
bare-key = "value" # Valid, uses letters and a dash
1234 = "value" # Valid, but 1234 is treated as a string
# invalid bare keys
= "value" # as we see here no key name is used, so bare key can't be empty
Quoted keys
Quoted keys are keys that are enclosed in quotes (either single '
or double "
quotes). They can follow the same rules as basic strings or literal strings. This means you can use a wider variety of characters for the key name, like spaces or special symbols, which are not allowed in bare keys.
"key with space" = "value" # Valid, space allowed in quoted key
"key@#%" = "value" # Valid, special characters allowed
Best practice: It’s usually better to use bare keys (without quotes) because they are simpler and more common. Use quoted keys only when you really need them, like when your key has special characters that bare keys can’t handle.
Also, as we know, bare key must not be empty, However, we can have an empty quoted key (a key with quotes), but it’s not recommended to do so.
For example, this is valid but not ideal:
"" = "value" # Empty quoted key is allowed but discouraged
'' = 'blank' # again VALID but discouraged
So, while empty quoted keys are allowed, it’s better to avoid them if possible!
Note: Bare keys and quoted keys essentially mean the same thing—they’re just two different ways to represent the same key. However, we can’t use both in the same place. It needs to be either one or the other, but not both. For example, this won’t work:
# THIS WILL NOT WORK
spelling = "favorite"
"spelling" = "favourite"
Dotted keys
Dotted keys allow you to organize properties into groups by separating them with a dot (.
). You can use either bare or quoted keys, and it helps structure related data, like categories or sub-properties.
name = "Orange" # A single key
physical.color = "orange" # Grouped under 'physical'
physical.shape = "round" # Another property under 'physical'
site."softaai.com" = true # A key with a dot in it (quoted)
For better understanding, this is how it would look in JSON:
{
"name": "Orange",
"physical": {
"color": "orange",
"shape": "round"
},
"site": {
"softaai.com": true
}
}
Whitespace rules:
- Whitespace around the dots is ignored. However, it’s better to avoid extra spaces for clarity.
- Indentation (spaces at the beginning of a line) doesn’t matter and is ignored.
- These are all treated the same:
fruit.name = "banana" # Best practice
fruit. color = "yellow" # Same as fruit.color (extra space)
fruit . flavor = "banana" # Same as fruit.flavor (extra space)
Ordering rule:
While it’s technically valid to define dotted keys out-of-order, it’s discouraged because it can make your code harder to read and understand. When you define the properties of related items (like apple
and orange
), it’s better to keep them grouped together.
Valid but discouraged:
apple.type = "fruit"
orange.type = "fruit"
apple.skin = "thin"
orange.skin = "thick"
apple.color = "red"
orange.color = "orange"
Recommended (better approach):
apple.type = "fruit"
apple.skin = "thin"
apple.color = "red"
orange.type = "fruit"
orange.skin = "thick"
orange.color = "orange"
Dotted key & Table Rule:
Before going into the rules, understanding the table in TOML is important. We will look at the table in detail in the respective section, but for now, just understand the basics so that it will help in grasping the dotted key rules.
Tables are used to group related key-value pairs. There are two types of tables based on their usage: regular tables and nested tables.
- Nested Tables: These tables use dot notation to organize hierarchical data.
- Inline Tables: Mentioned briefly, inline tables are used for small data sets with a simpler syntax.
# Regular Table server
[server]
host = "127.0.0.1"
port = 8080
# Nested Table
[server.logging]
level = "debug"
output = "stdout"
# Inline Table
user = { name = "amol", age = 28 }
Rule 1
In TOML, if a key hasn’t been defined yet, you can still create it as a table and add properties to it. When you use dotted keys, you’re essentially creating nested structures.
fruit.apple.smooth = true # This creates a 'fruit' table with a sub-table 'apple', and inside that, 'smooth' as a key
fruit.orange = 2 # Adds a new key 'orange' to the 'fruit' table
However, if you define a key with a value (like a number), you can’t later treat it as a table.
For example, this won’t work:
fruit.apple = 1 # Sets 'apple' inside 'fruit' as an integer (not a table)
fruit.apple.smooth = true # ERROR! Now 'apple' is an integer, not a table, so you can't add 'smooth' to it
In short, you can add to a table using dotted keys, but you can’t change a value into a table once it’s set.
Rule 2
You can technically use numbers in bare keys, which means you could create a dotted key that looks like a decimal or float number. For example:
3.14159 = "pi"
But, this isn’t actually a single key with a decimal point. Instead, TOML interprets this as two separate keys (3
and 14159
) under a table structure. It would look like this in JSON:
{
"3": { "14159": "pi" }
}
So, don’t use numbers like this unless you really need it, because it can be confusing. It’s better to avoid this and just use standard naming for your keys!
![](https://softaai.com/wp-content/uploads/2024/11/BigSectionSeparator-1024x64.png)
TOML Strings
In TOML, strings are sequences of characters enclosed in quotes. They are used to represent textual data, such as names, URLs, file paths, or any other information that needs to be stored as plain text.
TOML supports the following types of strings:
- Basic Strings
- Multiline Basic Strings
- Literal Strings
- Multiline Literal Strings
Each of these string types serves a specific purpose and offers flexibility in handling text data. Let’s explore each type with examples and detailed explanations.
Basic Strings
Basic strings in TOML are enclosed in double quotes ("
). These are ideal for simple textual data and support escape sequences.
Basic strings in TOML are enclosed in double quotes ("
). These are ideal for simple textual data and support escape sequences for special characters, including:
Unicode characters: You can use almost any character from any language, including symbols and letters.
Escaped characters: Some characters have special meanings and cannot be used directly in a string. These include:
- The quotation mark (
"
) - The backslash (
\
)
Control characters: These are invisible characters such as newlines, tabs, and others in the range U+0000 to U+001F, U+007F (e.g., null, backspace).
If you need to include any of these characters in your string, you’ll need to escape them using a backslash (\
). For example:
\"
for a quotation mark.\\
for a backslash.
Syntax
key = "This is a basic string."
Features
- Escape sequences like
\n
,\t
, and\"
are supported. - Suitable for most common cases where special characters need to be encoded.
str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
This given string would look like this when processed (it’s just for the sake of understanding).
I'm a string. "You can quote me". Name José
Location SF.
Here’s a simple explanation of the most popular escape sequences and how they work:
\b
– Backspace (U+0008)
Removes the previous character, often used for corrections.\t
– Tab (U+0009)
Adds a horizontal tab, useful for aligning text or creating indents.\n
– Linefeed (U+000A)
Moves the text to a new line, commonly used to break lines in a string.\f
– Form Feed (U+000C)
Used for page breaks in older printers or systems but is rarely seen today.\r
– Carriage Return (U+000D)
Moves the cursor to the beginning of the line, often used in combination with\n
for new lines on Windows (\r\n
).\"
– Quote (U+0022)
Allows you to include double quotes ("
) inside a string without ending it.\\
– Backslash (U+005C)
Lets you include a backslash in the string.\uXXXX
– Unicode (U+XXXX)
Represents a Unicode character using 4 hexadecimal digits. For example,\u00E9
is the Unicode for “é.”\UXXXXXXXX
– Unicode (U+XXXXXXXX)
Represents a Unicode character using 8 hexadecimal digits, suitable for higher Unicode scalar values.
Key Notes:
- Any escape sequences not listed above are reserved, and using them should result in an error in TOML.
- Any Unicode character can be escaped using the
\uXXXX
or\UXXXXXXXX
forms, as long as the code is a valid Unicode scalar value. For example,\u0041
represents “A”.
By using these escape sequences, you can handle formatting, symbols, and special characters more effectively in TOML strings, keeping your configuration files both clear and versatile.
Multiline Basic Strings
TOML makes it convenient to work with long passages of text or multi-line strings by using multi-line basic strings. These strings are enclosed with three double quotation marks ("""
) on each side. Here’s how it works:
Allowing Newlines:Multi-line basic strings allow text to span multiple lines, making them ideal for storing long passages (like translations or formatted text).
str1 = """
Roses are red
Violets are blue"""
Trimming Leading Newline:
If there’s a newline immediately after the opening """
, it will be trimmed, ensuring the text starts neatly.
Whitespace and Newline Handling:
Any other whitespace or newline characters within the text are preserved, allowing for proper formatting.
Platform-Specific Newline Normalization:
TOML parsers may normalize newlines (\n
for Unix-like systems or \r\n
for Windows) depending on the platform.
# On a Unix system, the above multi-line string will most likely be the same as:
str2 = "Roses are red\nViolets are blue"
# On a Windows system, it will most likely be equivalent to:
str3 = "Roses are red\r\nViolets are blue"
Line Ending Backslash (\
):
If you want to write long strings without adding extra spaces or newlines, you can use a line ending backslash (\
). When a line ends with an unescaped \
, it removes any following spaces, newlines, or tabs until the next visible character or closing delimiter.
The following strings are identical in value,
# The following strings are byte-for-byte equivalent:
str1 = "The quick brown fox jumps over the lazy dog."
str2 = """
The quick brown \
fox jumps over \
the lazy dog."""
str3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
So, what does the rule say?
Any Unicode character is allowed, except those that require escaping, such as the backslash (\
) itself and certain control characters other than tab, line feed, and carriage return (U+0000 to U+0008, U+000B, U+000C, U+000E to U+001F, U+007F).
There is another rule about quotation marks: You can use either single or double quotation marks anywhere inside a multi-line string.
Let’s explore this in depth with examples and explanations.
How Quotation Marks Work in Multi-Line Basic Strings
In TOML, multi-line basic strings use triple double quotes ("""
) to allow text to span multiple lines. However, using multiple consecutive quotation marks ("
) inside them can be tricky because it may conflict with the string delimiters. Let’s go through each example carefully.
Valid Example
str = """Here are two quotation marks: "". Simple enough."""
Here,
- The string is enclosed in triple double quotes (
"""
), so it’s a valid multi-line string. - Inside the string,
""
(two quotation marks) appear normally without causing any issues.
Invalid Example (incorrect syntax)
# str = """Here are three quotation marks: """."""
Why is this invalid?
- The first triple double quote (
"""
) starts the string. - The next three quotes (
"""
) end the string too early. - Then, TOML sees
.""""
as invalid syntax outside the string.
Solution: Use an escape sequence to avoid closing the string unintentionally.
Corrected Example (escaping quotes)
str = """Here are three quotation marks: ""\"."""
Here,
- The first two quotes
""
appear normally. - The third quote
\"
is escaped, preventing the string from closing too early. - The final
"""
properly ends the string.
Including Many Quotes (Escaping for Clarity)
str = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""
Here,
- Inside the string, we want to include
"""""""""""""
(15 double quotes). - To avoid accidentally ending the string, we escape every third quotation mark (
\"
). - This allows TOML to correctly parse the string while still displaying 15 quotes inside it.
Quotes Inside a Sentence
str = """"This," she said, "is just a pointless statement.""""
As you see here,
- The string starts and ends with triple double quotes (
"""
), which allow multi-line text. - The inner text contains regular double quotes (
"This,"
and"is just a pointless statement."
), which do not conflict with the string delimiters. - Since the first character inside the string is a quotation mark (
"
), it does not close the string because TOML allows this.
Alternative: Using Literal Strings to Avoid Escaping
If escaping feels complicated, TOML also supports literal strings, which use triple single quotes ('''
). These do not process escape sequences, meaning you can write anything inside without worrying about backslashes or quote conflicts.
str_literal = '''Here are three quotation marks: """.'''
Why is this easier?
- No need to escape quotes (
\
)—everything is taken as is. - Ideal for Windows paths, regex patterns, or text with many quotes.
Literal Strings
Single-Line Literal Strings
- Surrounded by single quotes (
'
). - No escaping (
\
) is allowed—everything inside is taken as it is. - Must be on a single line (no multi-line support).
# What you type is exactly what you get.
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Amol "J" Pawar'
regex = '<\i\c*\s*>'
No need to escape backslashes (\
) or quotes ("
), making it useful for file paths and regex patterns.
Though it looks impressive, there is one limitation. Let’s see what it is and find a solution for it.
Limitation: Single Quote ('
) Cannot Be Used Inside
Since escaping is not allowed, you cannot include a single quote ('
) inside a single-quoted string.
This is NOT possible:
bad_example = 'I'm using TOML' # INVALID because of the `'` in "I'm"
The workaround for this is to use multi-line literal strings. Let’s see it in detail.
Multi-Line Literal Strings ('''
)
- Surrounded by triple single quotes (
'''
). - No escaping is needed, and backslashes (
\
) are taken as it is. - Supports multiple lines while preserving whitespace and newlines.
- The first newline after
'''
is trimmed (ignored).
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
Again, why is this useful?
- It allows single quotes (
'
) inside the string, unlike single-line literal strings. - Also, whitespace and newlines are preserved exactly as they appear.
Limitation: Three Consecutive Single Quotes ('''
) Are Not Allowed
A sequence of three or more single quotes ('''
) inside a multi-line string is not permitted because it would conflict with the string delimiters.
This is NOT valid:
# INVALID because `''''''''''''''` is inside a multi-line literal string
apos15 = '''Here are fifteen apostrophes: ''''''''''''''''''
Workaround: Use a double-quoted basic string ("""
), which allows escaping:
apos15 = "Here are fifteen apostrophes: '''''''''''''''"
Handling Quotes in Multi-Line Strings
You can include one or two single quotes ('
), but not three or more in a row.
quot15 = '''Here are fifteen quotation marks: """""""""""""""'''
str = ''''That,' she said, 'is still pointless.''''
The first and last quote ('
) are just part of the string—they don’t interfere.
Control Characters Are Not Allowed
TOML does not allow control characters (except for tab).
- This means you can’t store binary data directly in a literal string.
- Use Base64 or another encoding if needed.
![](https://softaai.com/wp-content/uploads/2024/11/BigSectionSeparator-1-1024x64.png)
Understanding Numbers in TOML
TOML supports two primary numeric types:
- Integers: Whole numbers without a fractional or decimal part.
- Floats: Numbers that include decimal points or are represented in scientific notation.
These numbers are used in configuration files for various applications, such as specifying limits, settings, or numeric identifiers.
TOML Integers
Integers in TOML are whole numbers. They can be:
- Positive (e.g.,
+99
,42
) - Negative (e.g.,
-17
) - Zero (e.g.,
0
,+0
,-0
→ all are the same)
int1 = +99
int2 = 42
int3 = 0
int4 = -17
Note – Positive integers can be prefixed with a plus sign, while negative integers are always prefixed with a minus sign. Additionally, the integer values -0 and +0 are valid and identical to an unprefixed zero.
Large Numbers with Underscores
For large numbers, underscores may be used between digits to enhance readability. Each underscore must be surrounded by at least one digit on both sides. That means you can use underscores (_) between digits to improve readability, but they must not appear at the start or end of the number (e.g., 1_000_000
is valid, but _1000
is not).
int1 = 1_000 # Same as 1000
int2 = 5_349_221 # More readable
int3 = 53_49_221 # Indian-style grouping
int4 = 1_2_3_4_5 # Allowed, but not recommended
We’ll look at a few examples of valid and invalid numbers in just a moment. But let’s first see, what exactly is Indian-style grouping? As an Indian, I’m pretty familiar with it, but I realize that not everyone might be, so let me break it down for them. By the way, I’m really proud of my heritage, and I always enjoy sharing a bit of our culture, ethics, and values with the world whenever I can. I think we all feel that way, right..?
So, what is Indian number system grouping?
The Indian number system is a way of grouping numbers that is different from the international system used in many other countries. The key difference lies in how large numbers are grouped and named.
Grouping:
- First three digits: The first three digits from the right are grouped together (ones, tens, hundreds).
- Subsequent groups: After the first three digits, the remaining digits are grouped in sets of two.
For example:
- 1,00,000 (one lakh) instead of 100,000
- 10,00,000 (ten lakh) instead of 1,000,000
- 1,00,00,000 (one crore) instead of 10,000,000
Commas are used to separate the groups of digits. The first comma is placed after the first three digits from the right, and subsequent commas are placed after every two digits.
The number 123,456,789 would be written as 12,34,56,789 in the Indian number system.
In short, the Indian number system has a unique way of grouping digits and uses specific place values to represent large numbers. It is typically used in neighboring countries like Nepal, Bangladesh, Pakistan, and other South Asian countries.
Valid Integers
positive_int = 42
negative_int = -100
large_number = 1_000_000 # Readable format
Invalid Integers
invalid_int = 1.0 # Not an integer (it's a float)
leading_zero = 007 # Leading zeros are NOT allowed
Leading Zeros: TOML does not allow integers to start with 0
unless the value is 0
itself.
Other Number Formats
TOML supports hexadecimal, octal, and binary for non-negative integers.
- Hexadecimal (base 16) →
0x
prefix - Octal (base 8) →
0o
prefix - Binary (base 2) →
0b
prefix
Key Rules:
- No
+
sign is allowed at the beginning. - Leading zeros are allowed (after the prefix).
- Hexadecimal values are not case-sensitive (uppercase and lowercase letters work the same).
- Underscores (
_
) can be used to improve readability, but not between the prefix and the number.
# Hexadecimal (Base 16) – Prefix: 0x
hex1 = 0xDEADBEEF # Same as 3735928559 in decimal
hex2 = 0xdeadbeef # Same as above, case-insensitive
hex3 = 0xdead_beef # Same as above, underscore for readability
# Octal (Base 8) – Prefix: 0o
oct1 = 0o01234567 # Same as 342391 in decimal
oct2 = 0o755 # Common in Unix file permissions
# Binary (Base 2) – Prefix: 0b
bin1 = 0b11010110 # Same as 214 in decimal
Integer Limits
- TOML supports 64-bit signed integers (from
-2^63
to2^63 - 1
), meaning numbers can range from −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. - If a number goes beyond this range, it cannot be stored correctly (some digits would be lost).
- In such cases, TOML must show an error instead of trying to adjust or round the number.
Valid 64-bit signed integers
small_number = -9223372036854775808 # Minimum value
large_number = 9223372036854775807 # Maximum value
Invalid (too large or too small)
too_large = 9223372036854775808 # Error! Exceeds the maximum limit
too_small = -9223372036854775809 # Error! Below the minimum limit
If you try to use a number outside this range, TOML must stop and show an error instead of storing an incorrect value.
Please note that some TOML parsers or validators might accept values beyond this limit. However, those that strictly follow the specification will show errors if the limit is exceeded.
TOML Floats
TOML floats follow the IEEE 754 binary64 format (same as double-precision floating-point numbers in many programming languages).
How Floats Are Written in Toml:
A float must have an integer part, followed by:
- A fractional part (a decimal point + digits) OR
- An exponent part (
E
ore
+ integer) OR - Both (fractional part first, then exponent).
Valid Floats
# Floats with a Fractional Part
flt1 = +1.0 # Positive float
flt2 = 3.1415 # Pi approximation
flt3 = -0.01 # Negative float
# Floats with an Exponent Part (scientific notation)
flt4 = 5e+22 # 5 × 10^22
flt5 = 1e06 # 1 × 10^6 (same as 1,000,000)
flt6 = -2E-2 # -2 × 10^(-2) (same as -0.02)
# Floats with Both Fractional and Exponent Parts
flt7 = 6.626e-34 # 6.626 × 10^-34 (Planck's constant)
Invalid floats
# INVALID examples
invalid_float_1 = .7 # Missing whole number (No digit before the decimal)
invalid_float_2 = 7. # Missing fractional part (No digit after the decimal)
invalid_float_3 = 3.e+20 # Missing fractional part before 'e' (No digit after the decimal)
Rule: A decimal point must have digits on both sides. This means a float must have digits on both sides of the decimal point.
Readability with Underscores
Similar to integers, underscores may be used to enhance readability. Each underscore must be surrounded by at least one digit.
# Readable Floats Using Underscores
flt8 = 224_617.445_991_228 # Underscores improve readability
Rule: Underscores must be between numbers, not at the start or end.
Zero in Toml Float
In TOML, the values -0.0
and +0.0
are valid and represent zero, but with a sign.
-0.0
: Negative zero+0.0
: Positive zero0.0
: Zero without any sign
According to the IEEE 754 standard for floating-point numbers:
-0.0
and+0.0
are treated as the same value (0.0
), but they have different internal representations, which can be important in certain calculations.- The sign of zero may affect certain edge cases in mathematical operations, but for most practical uses, they are treated the same as
0.0
.
This is mostly used in scientific computing or cases where the sign of zero can have meaning.
Special Float Values
TOML also supports special float values, which are always written in lowercase.
Infinity (inf
)
inf
: Represents positive infinity.+inf
: Same asinf
, positive infinity (optional + sign).-inf
: Represents negative infinity.
# infinity
sf1 = inf # positive infinity
sf2 = +inf # positive infinity (same as inf)
sf3 = -inf # negative infinity
Infinity is often used in mathematical operations to represent values that exceed any finite number.
Not-a-Number (nan
)
nan
: Represents “Not a Number,” which is used to signal invalid or undefined numbers (like dividing zero by zero).+nan
: Same asnan
, but with an optional plus sign.-nan
: Same asnan
, but with a minus sign.
# not a number, 0 / 0 results in nan because there's no defined answer to that division.
sf4 = nan # actual sNaN/qNaN encoding is implementation-specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation-specific
NaN is used when a result doesn’t make sense (like dividing zero by zero). The way NaN (Not-a-Number) is encoded depends on the implementation, so how it’s stored can vary. This means how NaN is stored in memory (internally) depends on the system or language you’re using.
There are two common types of NaN:
- sNaN (Signaling NaN): Used to signal an error in a computation (usually a floating-point exception or to trigger a trap, depending on the system).
- qNaN (Quiet NaN): Used to propagate through calculations without signaling an error (i.e., it propagates “quietly” through calculations without raising exceptions).
However, for TOML, it doesn’t matter whether it’s sNaN or qNaN; it simply stores it as nan
, and how it’s handled is up to the implementation (such as in programming languages or hardware).
![](https://softaai.com/wp-content/uploads/2024/09/BigSectionSeparator-1024x64.png)
TOML Date & Time
What is TOML Date & Time?
TOML provides native support for dates, times, and timestamps, adhering to the RFC 3339 standard, which is a stricter subset of ISO 8601. This format is widely used in programming languages, APIs, and logging systems.
Why Use RFC 3339 in TOML?
- Avoids timezone confusion when handling timestamps across regions.
- Supports fractional seconds for precise logging, debugging, and analytics.
- Provides a standard format for APIs, databases, and automation tools.
Types of Date-Time Representations in TOML
TOML supports four types of date-time representations:
- Offset Date-Time (Includes date, time, and timezone offset)
- Local Date-Time (Includes date and time without a timezone)
- Local Date (Only the date without time)
- Local Time (Only the time without date)
Let’s explore each of these in detail with examples.
Offset Date-Time
Offset Date-Time includes the date, time, and timezone offset from UTC. This is useful when you need to store timestamps with explicit timezone information.
A small but important question — what does ‘offset’ mean when defining date and time?
In date-time representation, an offset indicates the difference between a specific local time and Coordinated Universal Time (UTC). It’s usually written in the format ±hh:mm, where:
‘+’ means the time is ahead of UTC
‘-’ means the time is behind UTC
So, why is the offset important?
Offsets are essential for adjusting time across different regions. They help ensure accurate time conversions and prevent errors when working with time zones, logs, and scheduling across multiple locations.
# Offset Date-Time example
utc_datetime = 2025-02-03T14:30:00Z # Coordinated Universal Time (UTC)
india_datetime = 2025-02-03T14:30:00+05:30 # India (UTC+5:30)
ny_datetime = 2025-02-03T14:30:00-05:00 # New York (UTC-5)
Here,
2025-02-03
→ Represents the date (Year-Month-Day)T
→ Separates the date and time (can be replaced with a space)14:30:00
→ Represents the time in HH:MM:SS format- The
Z (Zulu time)
at the end ofutc_datetime
means it’s in Coordinated Universal Time (UTC with no offset). india_datetime
uses+05:30
, meaning it is 5 hours and 30 minutes ahead of UTC.ny_datetime
uses-05:00
indicating it is 5 hours behind UTC.
Additional Examples for Clarity
Let’s go through a few more examples to deepen our understanding and see how different scenarios are handled.
odt1 = 1979-05-27T07:32:00Z # UTC time (Zulu time)
odt2 = 1979-05-27T00:32:00-07:00 # Same instant, but in UTC-07:00
odt3 = 1979-05-27T00:32:00.999999-07:00 # Includes microseconds
# Alternate format using a space instead of 'T'
odt4 = 1979-05-27 07:32:00Z # Equivalent to odt1
odt1 = 1979-05-27T07:32:00Z
This means May 27, 1979, at 07:32:00 AM in UTC (Zulu time).
odt2 = 1979-05-27T00:32:00-07:00
This means May 27, 1979, at 00:32:00 AM in UTC-7 (Pacific Time Zone).
- If converted to UTC, it is 07:32:00 AM UTC (same as
odt1
).
odt3 = 1979-05-27T00:32:00.999999-07:00
Similar to odt2
, but includes microseconds (0.999999 seconds) for higher precision.
odt4 = 1979-05-27 07:32:00Z
Same as odt1
, but replaces T
with a space for readability.
Please note that:
You can replace the
T
between the date and time with a space for readability (e.g.,1979-05-27 07:32:00Z
).Fractional seconds (e.g.,
.999999
) are supported but not required. If extra precision is provided beyond the system’s capability, it should be truncated (cut off), not rounded.
Real-World Example: Server Logs
Imagine a log file that records important system events:
[server_restart]
event = "System Reboot"
timestamp = 2025-02-04T14:15:30.456789Z
[user_login]
user = "tony"
login_time = 2025-02-04T08:15:30-06:00 # tony logs in from a UTC-6 region
[database_backup]
backup_time = 2025-02-04T23:59:59.999999+05:30 # India Standard Time (UTC+5:30)
This ensures that every event has an unambiguous timestamp, regardless of the time zone. When timestamps include a time zone offset or are stored in UTC, they can be consistently converted and compared across different regions.
Local Date-Time (Without Time Zone)
A Local Date-Time includes the date and time but does not have any timezone information.
# Local Date-Time example
scheduled_at = 2025-02-15T09:00:00
2025-02-15T09:00:00
→ Represents date and time without timezone.- The given date-time values are local (not tied to UTC or any time zone).
- Without an offset (
Z
or±hh:mm
), these timestamps cannot be converted to a global instant without additional context (e.g., knowing the intended timezone). - Used for cases where timezone conversion is not needed, such as for timestamps in local logs.
Use Case: Event Scheduling in a Local System
Imagine you’re building a conference scheduling system where sessions are stored in a TOML file. Since the schedule is based on the local time at the venue (not tied to UTC), we omit the timezone offset.
# Conference schedule stored in local time
conference = "softAai Tech Summit 2025"
# Sessions without timezone (assumed to be in the local conference timezone)
session1_start = 2025-06-15T09:00:00 # 9:00 AM
session1_end = 2025-06-15T10:30:00.500 # 10:30 AM with 500 milliseconds
session2_start = 2025-06-15T11:00:00 # 11:00 AM
session2_end = 2025-06-15T12:30:00.123 # 12:30 PM with 123 milliseconds
# Break time
lunch_start = 2025-06-15T12:30:00
lunch_end = 2025-06-15T13:30:00
Here,
- The times do not include a timezone, meaning they are defined in local time (e.g., IST). This is fine as long as everyone knows the conference is in India and follows the same local time.
- If different users in different time zones view this, they will not get an accurate conversion unless they know the correct timezone.
- Microseconds (e.g.,
.500
and.123
) provide precision but may be truncated in some implementations.
Milliseconds (
.SSS
) are required, but beyond that (microseconds/nanoseconds) is implementation-specific. If an application doesn’t support high precision, extra precision is truncated (cut off) instead of rounded.
Impact of Including a Timezone
What happens when a user from London checks the schedule? If the timezone offset isn’t included, they might be unsure whether the times are in UTC, IST, or another local time. To avoid confusion, it’s best to include an offset to make the times clear.
If you want to ensure everyone interprets the time correctly, always include a timezone offset:
# Session timings with time zone offsets
session1_start = 2025-06-15T09:00:00+05:30 # 9:00 AM IST (UTC+5:30)
session1_end = 2025-06-15T10:30:00.500+05:30 # 10:30 AM IST (with milliseconds)
session2_start = 2025-06-15T11:00:00+05:30 # 11:00 AM IST
session2_end = 2025-06-15T12:30:00.123+05:30 # 12:30 PM IST (with milliseconds)
Now, even if someone from New York or London checks the schedule, they will understand exactly what time it corresponds to in their local timezone by calculating the difference from the provided offset.
So,
Use local date-time (without offset) when:
- The time is fixed to a specific place (e.g., event in one location).
- You don’t need to convert it globally.
And use date-time with an offset when:
- You need to convert times across time zones.
- The event can be attended globally.
Local Date (Date Without Time)
Sometimes, you only need a date without specifying a time.
# Local Date example
release_date = 2025-07-20
2025-07-20
→ Represents an entire day (July 20, 2025) without any connection to time zones or time offsets.
This format is useful when you want to record an event or action that occurred on a specific day but don’t need to worry about the exact time or time zone. For example, birthdays, holidays, or historical events can be recorded simply by their date, without the need to specify the exact time. The day itself remains the same, regardless of where you are in the world.
In short, a Local Date is:
- A representation of just the date, without time or time zone.
- Used for events or information tied to a specific day, not a particular moment in time.
Local Time (Time Without Date)
Local Time, on the other hand, focuses purely on the time of day, without any reference to a specific date or time zone. This format captures only the hour, minute, second, and possibly fractions of a second, but it is not concerned with the day or any time zone offset.
# Local Time example
backup_time = 23:45:00
23:45:00
→ Represents time in HH:MM:SS format- Useful for defining daily recurring events like alarms or job schedules.
The idea behind Local Time is that you may want to capture a precise time of day without associating it with any specific date or time zone. For instance, when setting alarms or schedules, you might be concerned only with a specific time during the day, not a particular day or location.
However, an important note when dealing with Local Time is precision. In this case (23:45:00.999999
), the time is expressed in microsecond precision (down to six decimal places), but systems might not support this level of precision. If the system can’t handle such precision, it’s important to truncate (remove the extra digits) rather than round the value. Truncating means simply cutting off the extra digits, ensuring that the time stays within the system’s capabilities.
In short, Local Time is:
- A representation of just a time, without linking it to any specific date or time zone.
- Used when you need to capture a specific time of day without worrying about the date or location.
- Potentially expressed in very fine precision, but systems must truncate if they can’t support such precision.
![](https://softaai.com/wp-content/uploads/2024/09/BigSectionSeparator-1024x64.png)
TOML Arrays
What Is an Array in TOML?
In TOML, an array is simply a collection of values. You define an array by placing the values inside square brackets ([ ]
). These values can be of the same type, like a list of integers, or they can be mixed types, like strings, numbers, and objects. Arrays are an essential part of TOML, as they help organize data in a readable and structured way.
Basic Array Syntax in TOML
Let’s start with the basics of arrays in TOML. Here’s the syntax:
array_name = [ value1, value2, value3 ] # it's just syntax so toml validation failed
The values inside the array are separated by commas, and the array itself is enclosed in square brackets. Whitespace (spaces and newlines) between the elements doesn’t affect the array, so you can format the array in any way that makes it more readable.
Examples
Simple Array of Integers
integers = [ 1, 2, 3, 4 ]
Here,
integers
is the name of the array.- The array contains four integer values:
1
,2
,3
, and4
.
Simple Array of Strings
fruits = [ "apple", "banana", "cherry" ]
fruits
is an array of strings.- The array contains the values
"apple"
,"banana"
, and"cherry"
.
Mixed-Type Arrays in TOML
TOML arrays are not limited to a single data type. You can mix different types of values in a single array. This allows for more complex data structures, depending on your needs.
Mixed-Type Array with Numbers and Strings
mixed_array = [ 1, 2.5, "hello", 3.7, "world" ]
In this array:
- The first element is an integer (
1
). - The second element is a floating-point number (
2.5
). - The third element is a string (
"hello"
). - The fourth element is another floating-point number (
3.7
). - The fifth element is a string (
"world"
).
By allowing mixed types, TOML provides flexibility to store various data formats in one array.
Nested Arrays in TOML
You can create more complex arrays by nesting arrays inside other arrays. A nested array is an array that contains other arrays as its elements.
Nested Arrays of Integers
nested_integers = [ [ 1, 2 ], [ 3, 4, 5 ] ]
Here,
nested_integers
is an array that holds two sub-arrays.
- The first sub-array is
[ 1, 2 ]
. - The second sub-array is
[ 3, 4, 5 ]
.
This makes nested_integers
a two-dimensional array, where each element is itself an array.
Nested Arrays with Mixed Types
mixed_nested = [ [ 1, 2 ], ["a", "b", "c"] ]
Here,
mixed_nested
is an array with two elements:
- The first element is an array of integers
[1, 2]
. - The second element is an array of strings
["a", "b", "c"]
.
TOML allows you to mix different data types within nested arrays, which adds flexibility when organizing data.
Arrays of Objects in TOML
One of the powerful features of TOML arrays is that they can store objects as their elements. Objects in TOML are essentially key-value pairs, and they are enclosed in curly braces ({ }
).
Array of Objects
contributors = [
{ name = "Amol", role = "Developer" },
{ name = "Niyati", role = "Designer" }
]
Here,
contributors
is an array of objects.
- Each object has two keys:
name
androle
. - The first object represents
"Amol"
, who is a"Developer"
. - The second object represents
"Niyati"
, who is a"Designer"
.
Arrays of objects allow you to group related data together in a structured way.
Array of Objects with Multiple Keys
team_members = [
"softAai Tech Team",
{ name = "Amol", email = "[email protected]", url = "https://amol.softaai.com" },
{ name = "Niyati", email = "[email protected]", url = "https://niyati.softaai.com" }
]
In this case:
- Each object contains
name
,email
, andurl
as keys, representing team members’ details. - An additional string value (
softAai Tech Team
) is added just to represent its mixed type array. It’s also possible.
Empty Arrays in TOML
TOML also supports empty arrays, which can be useful when you don’t have any values to store initially but still need to define the array.
empty_array = []
Here,
empty_array
is just an empty array with no values inside.
Formatting Arrays in TOML
TOML is flexible when it comes to formatting arrays. You can span arrays across multiple lines, add comments, and even include trailing commas. This makes TOML more user-friendly, especially for larger files.
Multi-line Array with Comments
numbers = [
1, # The first value
2, # The second value
3, # The third value
4 # The fourth value
]
Here,
- The array spans across multiple lines for readability.
- Each element has a comment next to it explaining what the value represents.
Trailing Comma in Arrays
numbers = [
1,
2,
3, # Trailing comma is allowed
]
The trailing comma after 3
is allowed, making it easier to add or remove values in the future without worrying about missing commas.
Newlines and Comments Before Values
numbers = [
# This is the first number
1,
# This is the second number
2,
# This is the third number
3
] # Closing bracket can also have a comment
In this example,
- Blank lines before values: Notice how there’s an empty line before the first value (
1
). - Comments before values: Each number has a comment above it.
- Closing bracket with a comment: The closing bracket (
]
) can also have a comment next to it.
This flexibility allows you to structure your TOML file in a way that’s easy to read and maintain without affecting the actual data.
Practical Use of Arrays in TOML
Arrays are frequently used in TOML files to represent lists of data, such as configurations for software, lists of contributors, or settings in an application. For example:
dependencies = [
"numpy",
"pandas",
"requests"
]
In this case:
dependencies
is an array listing the names of required libraries for a project.numpy
: A fundamental package for numerical computing in Python.pandas
: A powerful library for data manipulation and analysis.requests
: A library for making HTTP requests, essential for interacting with web APIs.
Configuring a Web Server
ports = [8080, 443, 3000]
allowed_ips = ["192.168.1.1", "10.0.0.2"]
ports
defines the available ports.allowed_ips
specifies which IPs can access the server.
![](https://softaai.com/wp-content/uploads/2024/09/BigSectionSeparator-1024x64.png)
TOML Tables
TOML Tables (also called dictionaries or hash tables) store data in key/value pairs, where a ‘key’ acts as a label for a specific ‘value.’ Tables are defined by headers, written in square brackets [ ] on their own line, which typically appear at the beginning of a table. This helps distinguish tables from arrays, which hold a list of values without labels or keys.
Basic Syntax of a TOML Table
[Table name]
key1 = value1
key2 = value2
key3 = value3
Note: The key difference between a normal list of key-value pairs and a table is that, in a table, the key-value pairs are related to each other and grouped together under a common section. This is why they are denoted with a table header.
[table-1] # This is a table (a section)
key1 = "some string" # key1 stores a text value
key2 = 123 # key2 stores a number
[table-2] # Another table (a separate section)
key1 = "another string" # key1 has a different value here
key2 = 456 # key2 has a different number
TOML tables are like containers that hold key-value pairs. You can think of them as sections in a document, similar to headings in a text file.
Tables are created using square brackets ([ ]
):
- Example:
[table-1]
creates a table named table-1.
Key-value pairs inside a table define data:
key1 = "some string"
→ This meanskey1
has the text “some string”.key2 = 123
→ This meanskey2
has the number 123.
Different tables group different sets of key-value pairs:
table-1
has it’s own two key-value pairs:
key1
→"some string"
key2
→123
table-2
also has it’s own two key-value pairs:
key1
→"another string"
key2
→456
Note: Each table acts as a separate group of related information. Everything below a header belongs to that table until another header appears or the file ends. The keys inside one table do not affect another, and the key/value pairs within a table can be in any order and may not remain as originally written (meaning you can change their order as needed).
Simple Example: Storing User Information in a TOML Table
[User]
name = "amol"
age = 30
email = "[email protected]"
Here,
Table Header ([User]
):
- This defines a section called
"User"
, grouping related key-value pairs together. - Everything under this header belongs to this table.
Key-Value Pairs (name
, age
, email
):
- Each key represents a specific piece of information.
- Each value holds the actual data.
"amol"
is a string,30
is a number, and"[email protected]"
is another string.
Why Use a Table?
- Without a table, these key-value pairs would be unorganized and unrelated.
- The
[User]
header makes it clear thatname
,age
, andemail
all belong to one entity (User).
Types of Tables in TOML
TOML supports multiple types of tables:
- Standard Tables
- Nested Tables
- Inline Tables
- Array of Tables
- Empty Tables
- Top-Level Tables or Root Tables
- Tables with Dotted Keys
Let’s go through each type in detail (We will explore Inline Tables and Arrays of Tables in more detail in later sections).
Standard Tables
These are the most common tables in TOML. They are defined using square brackets ([]
) and help in grouping related configurations.
#This defines a server table with two key-value pairs.
[server]
ip = "192.168.1.1"
port = 8080
Here, server
is a table containing ip
and port
as key-value pairs.
Nested Tables
TOML allows you to nest tables inside other tables to create hierarchical data structures.
[database]
name = "myapp"
[database.connection]
host = "127.0.0.1"
port = 3306
Here, database.connection
is a nested table under database
. This defines a database
table containing a nested connection
table. This structure makes it easy to manage related settings. The equivalent hierarchical structure would look like:
database
├── connection
├── host = "127.0.0.1"
├── port = 3306
JSON representation:
{
"database": {
"name": "myapp",
"connection": {
"host": "127.0.0.1",
"port": 3306
}
}
}
Inline Tables
An inline table is a compact way to define key-value pairs in a single line. These are enclosed within curly brackets {}
and should be used for small datasets.
#Inline Table
info = { name = "amol", age = 30, email = "[email protected]" }
Here, info
is an inline table containing three key-value pairs.
Equivalent standard TOML Table Representation:
# This is equivalent to standard TOML Table (It's just for the sake of understanding)
[info]
name = "amol"
age = 30
email = "[email protected]"
Use Cases:
- Inline tables are best for defining small, one-liner configurations.
- They improve readability when used sparingly.
Note: Inline tables improve readability for small tables but should be avoided for large datasets.
Array of Tables
TOML also supports arrays of tables, which allow you to define multiple instances of a structured table. This is useful when you need a list of similar objects.
[[employees]]
name = "Amol"
position = "Engineer"
[[employees]]
name = "Niyati"
position = "Designer"
Each [[employees]]
block represents a separate table within the array.
Equivalent JSON Representation:
{
"employees": [
{"name": "Amol", "position": "Engineer"},
{"name": "Niyati", "position": "Designer"}
]
}
Here, we define multiple employees
, each as an entry in an array.
Empty Tables
Empty tables are allowed in TOML, meaning they can exist without any key/value pairs
[empty_table] # This table has no keys or values, and that's fine
This creates an empty table called empty_table
.
Top-Level Tables or Root Tables
The top-level table (or root table) is the first section of the document. It’s unique because it doesn’t have a header (no square brackets) and defines key-value pairs directly. It starts at the very beginning of the document and ends before the first named table (or the end of the file). Unlike other tables, it is nameless and cannot be relocated.
# Top-level table starts here
name = "Lalya"
breed = "Desi Indian Dog"
# Top-level table ends here
[owner] # This is a separate table after the root table.
name = "Amol Pawar"
member_since = 2024-08-04
Here,
- The root table holds the basic details, like
name
andbreed
. - The
[owner]
table follows and defines details about the dog’s owner.
Top-level tables define global information and appear before the first named table. They cannot be relocated. A top-level table is unnamed and should always be at the start of the file, before any named tables.
Tables with Dotted Keys
In TOML, dotted keys allow you to define tables at various levels within a hierarchy by using dot notation. TOML will create tables for each key part before the last one, as long as those tables haven’t already been defined.
Creating Tables with Dotted Keys
When you use dotted keys, they automatically create and define tables for each part of the key, up to the second-last part. This happens only if the tables were not previously created.
fruit.apple.color = "red" # Creates the "fruit" table and the "fruit.apple" table
In this case, TOML interprets this as:
- A
fruit
table (if it doesn’t exist). - A nested
fruit.apple
table (if it doesn’t exist). - A key-value pair for
color
insidefruit.apple
.
Defining Nested Tables with Dotted Keys
When you extend the dotted keys, TOML continues creating sub-tables as needed.
fruit.apple.taste.sweet = true # Creates fruit.apple.taste table
This results in:
- The
fruit
table (if not already defined). - The
fruit.apple
table (if not already defined). - The
fruit.apple.taste
table (if not already defined). - The key
sweet
insidefruit.apple.taste
with the valuetrue
.
Defining Tables and Sub-Tables: Restrictions
You cannot redefine a table that has already been created, either through a dotted key or using the [table “” not found /]
header.
Example (Incorrect/Invalid):
# Note – Considering the above examples, the table is already defined, so the following examples are invalid.
[fruit.apple] # Invalid, because fruit.apple has already been defined
color = "green"
[fruit.apple.taste] # Invalid, fruit.apple.taste already defined
sweetness = "high"
However, sub-tables can be added under an existing table, even if it was defined using dotted keys.
Defining Sub-Tables
If you’ve already used dotted keys to create tables, you can still use the [table “” not found /]
header to add sub-tables inside the already created tables.
[fruit] # This is the main table.
apple.color = "red"
apple.taste.sweet = true
[fruit.apple.texture] # Defines a sub-table for texture under apple.
smooth = true
Here,
- The
fruit
table is defined withapple.color
andapple.taste.sweet
. [fruit.apple.texture]
defines a sub-table withinfruit.apple
, and the keysmooth
is set totrue
.
Sub-Tables and Nested Headers
If you want to define sub-tables manually, you can use the [table “” not found /]
header. A sub-table can be defined by including a table header within an existing table.
[fruit]
apple = "red"
orange = "orange"
[fruit.apple.texture]
smooth = true
rough = false
Here,
- The main
fruit
table containsapple
andorange
entries. - The
[fruit.apple.texture]
sub-table defines properties related to the texture of apples (smooth
andrough
).
This feature helps to create a clear hierarchy in our configuration file, especially for more complex configurations.
Real-World Example: Fruit Configuration
In this example, we organize information about fruit using both dotted keys and [table “” not found /]
headers.
[fruit] # The main fruit table
apple.color = "red"
apple.taste.sweet = true
apple.size = "medium"
[fruit.apple.texture] # A sub-table for texture within apple
smooth = true
rough = false
[fruit.orange] # Another fruit: orange
color = "orange"
taste.sour = true
Equivalent JSON:
{
"fruit": {
"apple": {
"color": "red",
"taste": {
"sweet": true
},
"size": "medium",
"texture": {
"smooth": true,
"rough": false
}
},
"orange": {
"color": "orange",
"taste": {
"sour": true
}
}
}
}
So, in general, here are the key points to remember:
- Dotted keys automatically create tables for each key part (unless they’re already defined).
- Tables cannot be redefined once they exist.
- Sub-tables can be defined using
[table “” not found /]
headers within already created tables.
- Best practice: Use dotted keys for simple cases, but use the
[table “” not found /]
header when defining sub-tables for better clarity and organization.
Understanding Table Naming Rules in TOML
TOML table names follow the same rules as TOML keys. This means:
- They must be unique within the document.
- They can be quoted if they contain special characters (e.g., spaces or dots).
Example: Nested Tables with a Special Character in the Name
[dog."tater.man"]
type.name = "pug"
JSON representation:
{
"dog": {
"tater.man": {
"type": {
"name": "pug"
}
}
}
}
Here,
[dog."tater.man"]
(Nested Table with Quoted Key)
dog
is the top-level table."tater.man"
is a subtable insidedog
.- The quotes around
"tater.man"
are necessary because it contains a dot (.
), which TOML normally treats as a separator for nested tables.
type.name = "pug"
(Nested Key-Value Pair)
- Inside the
"tater.man"
subtable, we create another subtabletype
. - Inside
type
, we definename = "pug"
.
Note:
- Quoted table names allow you to use dots (
.
) as part of the name, instead of indicating nested tables. - Without quotes,
tater.man
would be interpreted as two nested tables (dog → tater → man
). However, by using quotes,"tater.man"
is treated as a single key underdog
, making it work similarly to JSON.
Whitespace and Formatting Rules
Whitespace around the key is ignored. Similarly, indentation is treated as whitespace and ignored. However, it is best practice to avoid extraneous whitespace.
[a.b.c] # Best practice: simple and clear
[ d.e.f ] # Same as [d.e.f] (extra spaces ignored)
[ g . h . i ] # Same as [g.h.i] (extra spaces ignored)
[ j . "ʞ" . 'l' ] # Same as [j."ʞ".'l'] (mixed quotes are allowed)
Here,
- Spaces don’t matter, but avoid unnecessary spaces to keep things readable.
- Quotes (
" "
,' '
) are allowed in table names, useful when special characters are needed.
So,
- Use dot notation (
.
) to create nested tables. - Whitespace around table names is ignored, but best practice is to keep it clean.
- Quotes can be used in table names if special characters are needed.
Avoid Defining a Table More Than Once
Just like keys, tables cannot be defined more than once. Doing so is invalid and will result in an error in TOML.
Incorrect Example 1:
You can’t redefine the [fruit]
table like this:
[fruit]
apple = "red"
[fruit] # This is invalid!
orange = "orange"
Incorrect Example 2:
This would also be invalid if you define a table and try to redefine part of it:
[fruit]
apple = "red"
[fruit.apple] # This is also invalid!
texture = "smooth"
So, how do we correct this? As we now know, if a table already exists, trying to define it again results in an error. To avoid this, we can use a different table name, like ‘fruits‘ instead of ‘fruit.’
[fruit] # Define fruit table
apple = "red"
[fruits.apple] # Nested table inside "fruits" for apple-specific info
texture = "smooth"
This is just an example for understanding purposes, but the key takeaway is to avoid re-defining the same table name more than once.
Defining Tables Out-of-Order (Valid but Discouraged)
Although TOML allows you to define tables in any order, it’s discouraged to do so. Organizing your tables properly makes your code more readable and maintainable.
Example (Valid but Discouraged):
You can technically define tables out of order, but it’s not considered best practice.
[fruit.apple] # Defining fruit.apple first
color = "green"
[animal] # Then defining animal
species = "dog"
[fruit.orange] # Finally defining fruit.orange
color = "orange"
While this is valid TOML syntax, the recommended approach is to group related tables together in order.
Example (Recommended):
It’s better to define related tables consecutively for clarity.
[fruit.apple] # First define fruit.apple
color = "green"
[fruit.orange] # Then define fruit.orange
color = "orange"
[animal] # Then define animal
species = "dog"
This way, it is easier to follow, especially for people who are reading or editing the file.
Super-Tables Not Required
In TOML, you don’t always have to specify every level of hierarchy for your tables. TOML can infer the parent tables (super tables) automatically, making things simpler for you.
Basically, TOML knows how to handle nested tables without you defining all of them:
If you specify [x.y.z.w]
, TOML automatically understands the hierarchy:
x
→x.y
→x.y.z
→x.y.z.w
.- You don’t need to declare
[x]
,[x.y]
, and[x.y.z]
for this to work. Just go straight to the deepest level you need.
[x.y.z.w] # This works without needing to define [x], [x.y], or [x.y.z]
You don’t need to define [x]
, [x.y]
, or [x.y.z]
in advance; TOML will infer them.
Defining a super-table afterward is fine:
- You can define a higher-level table after nested ones, and it will still work. TOML will connect them properly.
[x.y.z.w] # Defines the deepest nested table
[x] # This works even after defining [x.y.z.w]
![](https://softaai.com/wp-content/uploads/2024/09/BigSectionSeparator-1024x64.png)
Inline Tables
Here, we will look at how inline tables work and when you should use them. Let’s dive deep into the concept.
What Are Inline Tables?
Inline tables are essentially a shorthand way of defining tables in TOML. Normally, tables in TOML are defined with square brackets and the data is organized over multiple lines. However, inline tables let you define key-value pairs within a single line, wrapped in curly braces {}
. This format is particularly useful when you need to represent a small set of related data without the verbosity of a full table.
For instance, let’s consider a simple example of defining a person’s name using an inline table:
name = { first = "Amol", last = "Pawar" }
This is equivalent to the more traditional multi-line table definition:
[name]
first = "Amol"
last = "Pawar"
As we can see, inline tables provide a more compact syntax, making the configuration file shorter and cleaner when dealing with small, simple data structures.
Key Characteristics of Inline Tables
Here are some important points to remember about inline tables:
Single-Line Definition
An inline table must be defined on a single line. This is a key feature that distinguishes it from standard tables, which often span multiple lines.
point = { x = 1, y = 2 }
It’s important to note that inline tables are not meant to be split across multiple lines. If you find yourself wanting to break up the content, that’s a good indication that you should use a standard table instead.
No Trailing Commas
Inline tables do not allow a trailing comma after the last key-value pair. This means that the last entry should not be followed by a comma, unlike in many programming languages where trailing commas are allowed to make the syntax more flexible.
point = { x = 1, y = 2 } # Correct
# point = { x = 1, y = 2, } # Incorrect - trailing comma is not allowed
No Newlines Within the Table
Inline tables should not have any newlines unless they are inside the value itself. For example, if the value of a key is a string that includes line breaks, that’s perfectly fine. But you cannot have multiple key-value pairs or nested tables on separate lines within an inline table.
animal = { type.name = "dog" }
# Equivalent standard table
[animal]
type.name = "dog"
This is valid because it’s all contained in one line. However, splitting this into multiple lines would violate the format and is not allowed:
# This is incorrect:
# animal = {
# type.name = "dog"
# }
Limitations with Sub-Tables
Inline tables are fully self-contained. This means that all keys and their values (including nested tables) must be included within the inline table itself. You cannot define keys or sub-tables outside the curly braces. If you try to add new sub-tables or new keys outside of an inline table, it will result in an error.
For example:
[product]
type = { name = "Nail" }
# type.edible = false # INVALID
Here, trying to add type.edible
outside of the inline table definition is not allowed. If you need to define multiple levels of keys, you’ll have to resort to standard table syntax.
Also, inline tables cannot be used to add keys or sub-tables to an already-defined table. Means, inline tables must be fully defined when they are created. Once you define an inline table, you cannot later add new keys or sub-tables to it.
So, once you create an inline table, you CAN NOT add new keys or sub-tables to it later.
When to Use Inline Tables
Inline tables are best used for small, simple groupings of data that don’t require the complexity of a full table definition. They’re ideal for cases where:
- You want to keep the configuration compact and concise.
- The data structure is small and won’t benefit from being split into multiple lines or tables.
- The data is logically grouped together but doesn’t need to be nested or expanded.
For example, if you’re defining a point with x and y coordinates, an inline table works perfectly:
point = { x = 1, y = 2 }
However, if you find yourself needing more complex nested data or want to include many more key-value pairs, it’s better to use the standard multi-line table format:
[point]
x = 1
y = 2
Best Practices for Inline Tables
While inline tables are a powerful feature in TOML, they come with certain best practices to ensure your configuration files remain readable and maintainable:
- Use Inline Tables for Simple, Grouped Data
They should be reserved for cases where the data structure is simple and doesn’t require nesting. Avoid using them for complex, multi-level data structures. - Don’t Overuse Inline Tables
While they’re compact, excessive use of inline tables can make your TOML file harder to read and maintain, especially if it results in very long single lines. When in doubt, opt for standard tables. - Maintain Consistency
If you choose to use inline tables in your configuration, try to maintain consistency throughout the file. Mixing inline and standard tables in the same configuration might confuse the reader, especially if it’s not clear why one approach was chosen over the other.
![](https://softaai.com/wp-content/uploads/2024/09/BigSectionSeparator-1024x64.png)
Arrays of Tables in TOML
Here, we’ll break down how to use arrays of tables, subtables, and inline tables in TOML. We’ll explore some common mistakes, how TOML ensures data consistency, and how you can use these features effectively.
What are Arrays of Tables in TOML?
In TOML, a table is a collection of key-value pairs, similar to a dictionary or JSON object. An array of tables is a list of such tables, each having the same structure but different values.
This is useful when defining multiple items of the same type, such as a list of users, servers, configurations, or even a collection of products, fruits, or other items.
Syntax
Arrays of tables are defined using double square brackets [[ ... ]]
. Each set of double square brackets creates a new table in the array.
[[servers]]
host = "192.168.1.1"
port = 8080
[[servers]]
host = "192.168.1.2"
port = 9090
Here,
- We define an array of tables named
servers
. - Each
[[servers]]
block represents an entry in the array. - Each server has a
host
andport
.
The resulting structure in JSON would look like this:
{
"servers": [
{ "host": "192.168.1.1", "port": 8080 },
{ "host": "192.168.1.2", "port": 9090 }
]
}
One more simple example for clarity.
[[fruit]]
name = "Apple"
color = "Red"
[[fruit]]
name = "Banana"
color = "Yellow"
[[fruit]]
name = "Grapes"
color = "Green"
In JSON, this would look like:
{
"fruit": [
{ "name": "Apple", "color": "Red" },
{ "name": "Banana", "color": "Yellow" },
{ "name": "Grapes", "color": "Green" }
]
}
How It Works
Arrays of tables begin with the first occurrence of that header, which initializes the array and adds its first table element. Each subsequent occurrence creates and adds a new table element to the array, with tables inserted in the order they appear.
Means,
- First instance of
[[table_name]]
→ Defines the array and its first table element. - Subsequent instances of
[[table_name]]
→ Add more tables to the array. - Insertion order matters → The tables appear in the order they are written.
[[products]]
name = "Apple MacBook Air (M2)"
sku = 7393945837
[[products]] # empty table within the array
[[products]]
name = "Asus Zenbook 14X OLED"
sku = 393857482
color = "space gray" # For readability and clarity, a space was added above, but it is part of the same products table (which contains 'name' as 'Asus Zenbook 14X OLED')
Here,
[[products]]
: This creates the first table inside an array calledproducts
. The table has two keys:name
andsku
.[[products]]
(Empty Table): This is an empty table inside the sameproducts
array. No key-value pairs are defined here.[[products]]
(Another Table): This creates another table inside the same array. It hasname
,sku
, andcolor
as keys.
If you convert this TOML structure to JSON, it would look like this:
{
"products": [
{ "name": "Apple MacBook Air (M2)", "sku": 7393945837 },
{ },
{ "name": "Asus Zenbook 14X OLED", "sku": 393857482, "color": "gray" }
]
}
Notice how JSON represents the array of tables. Each object in the array corresponds to a table in TOML, and the empty table is represented as an empty object {}
in JSON.
Note :
- The
[[products]]
syntax is used to define an array of tables, with each table being a separate entry in the array. - The tables are ordered by their appearance, and you can have empty tables within the array as well.
Nested Arrays of Tables
First, let’s do a quick recap. As we know, we can define subtables within a table, allowing us to group related data under a single parent table. A subtable is essentially a table within a table and is indicated using dot notation ([parent.subtable]).
Now, when we reference an array of tables, it always refers to the most recently defined table element in that array. This behavior allows us to define subtables (nested tables) and even sub-arrays of tables (nested arrays of tables) within the last added table.
Let’s understand this:
- Each
[[array_name]]
creates a new table inside an array. - Any sub-table (
[array_name.subtable]
) or sub-array ([[array_name.subtable]]
) added immediately after a[[array_name]]
entry applies to the most recently created table.
Example 1: Defining Sub-Tables
[[company]]
name = "TechCorp"
[company.address]
city = "New York"
zip = "10001"
[[company]]
name = "softAai"
[company.address]
city = "Pune"
zip = "411001"
Equivalent JSON Representation:
{
"company": [
{
"name": "TechCorp",
"address": {
"city": "New York",
"zip": "10001"
}
},
{
"name": "softAai",
"address": {
"city": "Pune",
"zip": "411001"
}
}
]
}
Here, in the above TOML file,
[[company]]
defines an array of tables namedcompany
.- The first instance (
TechCorp
) gets a sub-tablecompany.address
with"New York"
. - The second instance (
softAai
) gets a differentcompany.address
with"Pune"
. - The
company.address
sub-table always applies to the last[[company]]
entry. As you can see, it belongs to the most recently added or created table instance, which is why it is part of the second element (softAai
) in the array of tables.
Example 2: Defining a Sub-Array of Tables
[[library]]
name = "Pune University Library"
[[library.books]]
title = "The Guide"
author = "R.K. Narayan"
[[library.books]]
title = "Malgudi Days"
author = "R.K. Narayan"
[[library]]
name = "Pune City Library"
[[library.books]]
title = "Godan"
author = "Munshi Premchand"
Equivalent JSON Representation:
{
"library": [
{
"name": "Pune University Library",
"books": [
{
"author": "R.K. Narayan",
"title": "The Guide"
},
{
"author": "R.K. Narayan",
"title": "Malgudi Days"
}
]
},
{
"name": "Pune City Library",
"books": [
{
"author": "Munshi Premchand",
"title": "Godan"
}
]
}
]
}
Here, in the above TOML file,
[[library]]
defines an array of libraries.- Each
[[library.books]]
entry belongs to the most recent[[library]]
. "Pune University Library"
has two books, while"Pune City Library"
has one book.- This results in a nested array of tables, which means you can have tables inside an array that itself contains another table.
Let’s look at a slightly more complex example, where we combine sub-tables and nested arrays of tables.
[[fruits]]
name = "mango"
[fruits.physical] # Subtable for the physical attributes of the fruit
color = "yellow"
shape = "oval"
[[fruits.varieties]] # Nested array of tables for the varieties of the fruit
name = "alphonso"
[[fruits.varieties]]
name = "dasheri"
[[fruits]]
name = "banana"
[[fruits.varieties]]
name = "robusta"
Here,
- The first
[[fruits]]
creates a table for a mango, with aname
field. - The
[fruits.physical]
subtable defines physical properties of the mango, like its color and shape. It’s nested within thefruits
table. - The
[[fruits.varieties]]
creates an array of tables to list different varieties of the mango, such as “alphonso” and “dasheri”. - Another
[[fruits]]
is added for a banana, which also has varieties defined.
Equivalent JSON Representation:
{
"fruits": [
{
"name": "mango",
"physical": {
"color": "yellow",
"shape": "oval"
},
"varieties": [
{ "name": "alphonso" },
{ "name": "dasheri" }
]
},
{
"name": "banana",
"varieties": [
{ "name": "robusta" }
]
}
]
}
In JSON, you can see that the fruits
array now contains objects, each of which can have other nested objects (like physical
) and arrays (like varieties
).
Common Mistakes and How to Avoid Them
While TOML is simple, there are some strict rules about how arrays and tables are defined. Here are a few common mistakes that TOML will throw errors for:
Parent Missing or Misplaced Order
# INVALID TOML DOC
[fruit.physical] # subtable, but to which parent element should it belong?
color = "red"
shape = "round"
[[fruit]] # parser must throw an error upon discovering that "fruit" is
# an array rather than a table
name = "apple"
Now, you may be wondering: Why is this invalid?”
- The subtable
[fruit.physical]
doesn’t know where it belongs because the parentfruit
table hasn’t been defined yet. - The
[[fruit]]
entry is incorrect because it tries to define a table inside an array, but thefruit
array was not set up beforehand.
In short, in TOML, a table (like
fruit
) must be defined before any subtables or arrays of tables inside it. The parser ensures that all parent elements exist before any child elements are defined.
Appending to an Already Defined Array
Basically, attempting to append to a statically defined array, even if the array is empty, should result in a parse-time error.
# INVALID TOML DOC
fruits = []
[[fruits]] # Not allowed
Here, you’re trying to append a new table [[fruits]]
to an already defined empty array (fruits = []
). Since fruits
is statically defined as an array, trying to add a table to it will result in an error.
Arrays and tables need to be defined in the correct order. Once an array is defined, you can’t mix the structure with normal tables or attempt to append a table to it after defining it.
Conflicting Array and Table Names
A conflict happens when you try to define a normal table using the same name as an existing array, which leads to a parse-time error. Similarly, if you try to turn a normal table into an array, it will also cause a parse-time error.
# INVALID TOML DOC
[[fruits]]
name = "mango"
[[fruits.varieties]]
name = "alphonso"
# INVALID: This table conflicts with the previous array of tables
[fruits.varieties]
name = "dasheri"
[fruits.physical]
color = "yellow"
shape = "oval"
# INVALID: This array of tables conflicts with the previous table
[[fruits.physical]]
color = "green"
Here,
- First part (
[[fruits]]
and[[fruits.varieties]]
): You define an array of tables with[[fruits]]
and a nested array[[fruits.varieties]]
for the different fruit varieties. - Error 1: The line
[fruits.varieties]
tries to redefinefruits.varieties
as a regular table. This is a conflict becausefruits.varieties
was already defined as an array of tables ([[fruits.varieties]]
). You can’t redefine it as a table. - Error 2: Similarly, the line
[[fruits.physical]]
tries to definefruits.physical
as an array of tables, but it was already defined as a regular table ([fruits.physical]
). This causes a conflict.
In TOML, if a structure is defined as an array or a table, you cannot redefine it as the opposite later.
So,
Rules for Arrays of Tables in TOML:
- Appending to arrays: You can’t append to a statically defined array (
[]
). The array must be explicitly defined before you add any tables to it. - Conflicts between arrays and tables: You cannot define a table with the same name as an array, nor can you redefine a table as an array, or vice versa.
Inline Tables in an Array (Compact Format)
As we know, inline tables are defined using curly braces {}
. However, we can also represent an array of inline tables (a list of objects), which is also valid in TOML. Here is a valid TOML format:
# Using Inline Tables
points = [
{ x = 1, y = 2, z = 3 },
{ x = 7, y = 8, z = 9 },
{ x = 2, y = 4, z = 8 }
]
Here,
points
is an array (list).- Each
{ x = ..., y = ..., z = ... }
is an inline table (a dictionary-like structure inside a list). - This is ideal when you have small data structures.
Now, how do we represent this using TOML’s standard array of tables format?
Alternatively, we can use TOML’s standard table array syntax ([[points]]
for multiple objects):
# Using Standard Table Array Syntax
[[points]]
x = 1
y = 2
z = 3
[[points]]
x = 7
y = 8
z = 9
[[points]]
x = 2
y = 4
z = 8
Here,
[[points]]
defines an array of tables, meaning multiple objects under points
.
Each [[points]]
entry creates a new table (object) with x
, y
, and z
values.
This format is easier to read when you have a large number of objects.
Both approaches are valid. The inline table format is more compact, while the table array format is more readable for larger datasets.
Why Choose TOML?
So, why should we use TOML for our configuration files? Here are the main reasons:
- Simplicity: TOML’s minimalistic design keeps things simple. You won’t find confusing syntax or complex features that complicate the file structure.
- Readability: With its clear key-value pairs and logical structure, TOML is easy to read and edit. Anyone, even without a technical background, can easily understand what’s going on in the file.
- Efficient Data Handling: The format’s design makes it easy to map the configuration data into hash tables, dictionaries, or maps, which are efficient to work with programmatically.
- Cross-Language Compatibility: TOML can be easily parsed into data structures in various programming languages, which makes it a great option for projects that may span multiple languages.
Conclusion
TOML is a configuration file format designed for simplicity and clarity. Its minimalist design keeps configuration files easy to read and understand, while the mapping to hash tables makes it easy to work with programmatically. Whether you’re working on a small project or a large application, TOML provides an intuitive and reliable way to handle configuration settings. Its compatibility with multiple programming languages also ensures that it can be used across different environments with ease.