TOML (Tom’s Obvious, Minimal Language) is a simple configuration file format designed to be easy to read and write. It is widely used in various applications, especially in the Rust ecosystem and modern software development workflows. One of TOML’s essential features is its table structure, which organizes data efficiently.
In this guide, we’ll learn about TOML tables types, including standard, inline, and array tables. Explore their syntax, structure, and best practices for efficient TOML configuration.
What Are TOML Tables?
At the core of TOML are tables, which store data in key-value pairs. A table in TOML is defined by a header enclosed in square brackets ([]
). The header marks the start of a new table, and the lines following it contain key-value pairs associated with that table. Tables can also be nested within one another, creating a hierarchical structure.
Basic Table Example
Let’s start with a simple example of a TOML file:
[fruit]
apple = "red"
orange = "orange"
Here, we have a table called [fruit]
, which contains two key-value pairs:
apple = "red"
orange = "orange"
TOML tables are unordered, meaning the order of key-value pairs within a table doesn’t matter. However, it’s still best practice to organize them in a logical, consistent way to improve readability.
TOML Tables Types
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.
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.
Conclusion
Understanding TOML tables is crucial for structuring configuration files effectively. Whether you use basic tables for simple key-value storage, nested tables for hierarchy, inline tables for compactness, or array of tables for lists, TOML provides a flexible and readable format for configurations.
By leveraging these different table types, you can create well-structured and maintainable TOML files for various applications.