Rustプロジェクトを開いたことがあれば、TOMLに出会っています。pyproject.tomlを使用する Pythonパッケージを触ったことがあれば、それも使っています。TOML — Tom's Obvious Minimal Language — は、YAMLのホワイトスペースの落とし穴とJSONのコメント不可に疲れた開発者を静かに勝ち取り続けている設定ファイル形式です。 GitHubの共同創設者Tom Preston-Wernerが作成したTOMLは、一つのアイデアを中心に設計されました:設定フォーマットは 仕様書なしで読めるほど明白でなければならない。その約束が守られているか見てみましょう。

TOMLとは正確に何か

TOMLは、公式仕様の冒頭に 明記された3つの明示的な設計目標を持つ設定ファイル形式です:読むのが明白で、複雑さが最小限で、 ハッシュテーブル(ほとんどの言語での辞書/マップ)に曖昧さなくマッピングされること。 3番目の目標が鍵です — すべての有効なTOMLファイルには正確に一つの正しい解析結果があります。 驚くような型強制なし、YAMLスタイルのブールの落とし穴なし、値が文字列か数値かの曖昧さなし。

フォーマットは古いINIファイルのセクションヘッダースタイルを借りていますが、その上に適切な型、配列、 ネストされたテーブルを追加しています。結果は設定ファイルを編集したことのある人なら誰にでも馴染みがあるものの、 パーサーが本物の型付きデータモデルを提供できるほどの構造を持っています。仕様のバージョン1.0.0は 何年もの改良の後、2021年1月にリリースされました — エッジケースを掘り下げたい場合は GitHubのTOML仕様を 参照できます。

基本:キー・バリューペアとコメント

TOMLファイルはキー・バリューペアで構成されています。キーと値は=で区切られ、 コメントは#で始まります。シンプルです。

toml
# Cargo.toml — Rust package manifest
[package]
name = "image-resizer"
version = "0.4.2"
edition = "2021"
authors = ["Ada Lovelace <[email protected]>"]
description = "Fast image resizing with Lanczos3 resampling"
license = "MIT"
repository = "https://github.com/example/image-resizer"

# Integers, floats, booleans — all native types
max_threads = 8
quality_default = 0.85
verbose_logging = false

ほとんどのキーにクォートは不要です。値は型付きです — 8は整数、0.85は浮動小数点、 falseはブール値。推測不要、値の見た目に基づく暗黙の型強制なし。 これが90%の時間で書く日常のTOMLです。

文字列の種類:基本、リテラル、複数行

TOMLには4種類の文字列があります。これですべての実際のケースをクリーンにカバーします:

toml
# Basic strings — double quotes, support escape sequences
greeting = "Hello, \nworld!"
path = "C:\\Users\\ada\\Documents"

# Literal strings — single quotes, no escape processing at all
regex_pattern = '\d{4}-\d{2}-\d{2}'
windows_path = 'C:\Users\ada'

# Multiline basic string — triple double quotes
sql_query = """
  SELECT user_id, email, created_at
  FROM users
  WHERE active = true
    AND created_at > '2024-01-01'
  ORDER BY created_at DESC
"""

# Multiline literal string — triple single quotes, no escapes
shell_script = '''
#!/bin/bash
echo "Deploying $APP_NAME to $ENV"
kubectl apply -f k8s/
'''

リテラル文字列型('シングルクォート')は存在を忘れがちですが、本当に役立ちます — 正規表現パターンとWindowsパスはバックスラッシュを二重にせずにはるかにクリーンです。 バックスラッシュが少なくなるクォートスタイルを選んでください。

数値、ブール値、日時

TOMLのネイティブ型は設定ファイルに実際に入れるものをすべてカバーしています。特に、 ファーストクラスのdatetimeサポートがあります — YAMLが技術的には持っているが、パーサー間で 一貫性なく処理するもの。

toml
# Integers — underscores allowed as separators (like numeric literals in code)
max_connections = 1_000_000
port = 5432
hex_color = 0xFF6B6B      # hex prefix supported
octal_permissions = 0o755  # octal prefix supported

# Floats
pi = 3.14159265
compression_ratio = 1.5e-3
infinity_val = inf         # special values: inf, -inf, nan

# Booleans — lowercase only (not True, TRUE, yes, on)
ssl_enabled = true
dry_run = false

# Datetimes — RFC 3339 format
created_at = 2024-03-15T09:30:00Z
updated_at = 2024-03-15T14:22:10+05:30
log_date = 2024-03-15           # local date (no time)
backup_time = 03:00:00          # local time (no date)
TOMLのブール値は厳密に小文字です。truefalseのみ — TrueTRUEyeson、またはYAML 1.1が 受け入れる他のバリアントは不可。これは意図的です。文字列"true"が必要なら、クォートしてください。

テーブル:INIスタイルのセクションヘッダー

TOMLのテーブルは[ヘッダー]構文で定義されます。ヘッダーの下にあるすべてのものは、 次のヘッダーが現れるまでそのテーブルに属します。これがTOMLを馴染み深く見せる機能です — 基本的に型付きINIファイルです。

toml
[database]
host = "db.internal"
port = 5432
name = "app_production"
pool_size = 20

[database.credentials]
username = "app_user"
# Don't put real passwords here — use env vars or a secrets manager
password_env = "DB_PASSWORD"

[server]
host = "0.0.0.0"
port = 8080
workers = 4

[server.tls]
enabled = true
cert_file = "/etc/ssl/certs/app.crt"
key_file = "/etc/ssl/private/app.key"

[database.credentials]のようなドット付きヘッダーはネストされたテーブルを作成します。 解析結果はまさに期待通りです:ネストされたcredentialsオブジェクトを持つdatabaseオブジェクト。 シンプルなケースにはインラインテーブルも書けます — 後述します。

配列とテーブルの配列

TOMLの配列は角括弧を使い、複数行にまたがることができます。TOMLの本当に特徴的な機能は テーブルの配列です — ダブルブラケット[[ヘッダー]]で定義されます。 これはTOMLの「JSONのように見えずにオブジェクトのリストをどう表現するか」への答えです。

toml
# Regular arrays — can be split across lines, trailing comma is fine
allowed_origins = [
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
]

supported_formats = ["jpeg", "png", "webp", "avif"]
retry_delays_ms = [100, 250, 500, 1000, 2000]

# Array of Tables — [[double brackets]]
# Each [[servers]] header appends a new object to the servers array
[[servers]]
name = "web-01"
ip = "10.0.1.10"
role = "primary"
tags = ["web", "prod"]

[[servers]]
name = "web-02"
ip = "10.0.1.11"
role = "replica"
tags = ["web", "prod"]

[[servers]]
name = "db-01"
ip = "10.0.2.10"
role = "primary"
tags = ["database", "prod"]

[[servers]]構文は3つのオブジェクトの配列として解析されます — JSONの"servers": [{...}, {...}, {...}]と同等です。JSONのオブジェクト配列と比べて冗長ですが、 各アイテムに多くのフィールドがある場合は読みやすさが利点です。このパターンは複数のバイナリターゲット、 例、ベンチエントリを定義する Cargo.tomlマニフェスト で多く見られます。

インラインテーブル:コンパクトな1行

テーブルがほんの数フィールドしかなく、そのためにセクションヘッダー全体を使いたくない場合、 インラインテーブルで1行で書けます:

toml
[build]
# Inline table — must stay on one line
target = { arch = "x86_64", os = "linux", libc = "musl" }

# Equivalent to writing:
# [build.target]
# arch = "x86_64"
# os = "linux"
# libc = "musl"

[feature_flags]
auth   = { enabled = true, rollout_pct = 100 }
search = { enabled = true, rollout_pct = 50 }
beta   = { enabled = false, rollout_pct = 0 }

インラインテーブルは1行に収まらなければならず、後で[ヘッダー]で拡張できません。 座標ペア、ビルドターゲット、シンプルなフラグ設定のような小さくてまとまった値グループに使ってください。 3〜4フィールドを超える場合は使わないでください;その時点では通常のテーブルの方が読みやすいです。

現実世界でのTOML

TOMLはRustとPythonエコシステムで強い地位を確立し、他の場所でも勢いを増しています。 日常でどこで遭遇するか:

  • Rust — Cargo.toml:すべてのRustプロジェクトがこれを持ちます。パッケージメタデータ、依存関係、機能、ビルドターゲットを定義します。Cargoマニフェストリファレンスは見つかる最も詳細な実世界のTOML使用ガイドです。
  • Python — pyproject.toml:PEP 518PEP 621がTOMLをPythonプロジェクトメタデータフォーマットとして標準化しました。Poetry、Hatch、PDM、setuptoolsはすべてここから読みます。
  • Deno:Deno設定ファイルはJSONに加えてTOMLをサポートします。
  • Hugo:静的サイトジェネレーターのHugoは設定とフロントマターフォーマットとしてTOMLを受け入れます — MarkdownファイルのトップにあるMarkdownファイルの+++デリミター間に見られます。
  • uv:AstralのファストPythonパッケージマネージャーはすべての設定にpyproject.tomlを使用し、新しいPythonツールのデファクト標準にしています。

フォーマット間で既存の設定を変換する必要がある場合、 TOMLからJSONJSONからTOML コンバーターが構造マッピングを行います。または TOMLフォーマッターを使って既存ファイルの不整合なスペースをクリーンアップし、 TOMLバリデーターで本番環境で表面化する前に構文エラーを検出してください。

PythonでのTOML解析

Python 3.11以降、標準ライブラリには tomllib が含まれています — 外部依存関係は不要です。Python 3.9と3.10には、 tomliバックポートが 同一のAPIを持ち、単一のインポートエイリアスで切り替えられます。

python
import sys

if sys.version_info >= (3, 11):
    import tomllib
else:
    import tomli as tomllib  # pip install tomli

# tomllib only reads binary mode — open with "rb"
with open("pyproject.toml", "rb") as f:
    config = tomllib.load(f)

# Types match TOML exactly: str, int, float, bool, datetime, list, dict
project_name = config["project"]["name"]               # str
python_requires = config["project"]["requires-python"] # str
dependencies = config["project"]["dependencies"]        # list[str]

print(f"Project: {project_name}")
print(f"Requires Python: {python_requires}")
print(f"Dependencies ({len(dependencies)}):")
for dep in dependencies:
    print(f"  {dep}")

# Parse from a string with tomllib.loads()
raw = """
[server]
host = "localhost"
port = 8080
debug = true
"""
server_config = tomllib.loads(raw)
print(server_config["server"]["port"])   # 8080 (int, not "8080")

tomllib.load()はバイナリモード("rb")を必要とすることに注意してください。 これは意図的です — TOMLはUTF-8エンコーディングを必要とし、バイナリモードで開くことでパーサー自体が エンコーディングチェックを処理できます。これは初回に人々を引っかける小さな落とし穴です。

RustでのTOML解析

Rustでは、tomlクレート が標準の選択です。serdeと密接に統合されており、最小限の定型コードで自分の構造体に直接デシリアライズできます:

rust
use serde::Deserialize;
use std::fs;

#[derive(Debug, Deserialize)]
struct AppConfig {
    server: ServerConfig,
    database: DatabaseConfig,
    feature_flags: FeatureFlags,
}

#[derive(Debug, Deserialize)]
struct ServerConfig {
    host: String,
    port: u16,
    workers: usize,
}

#[derive(Debug, Deserialize)]
struct DatabaseConfig {
    host: String,
    port: u16,
    name: String,
    pool_size: u32,
}

#[derive(Debug, Deserialize)]
struct FeatureFlags {
    enable_beta: bool,
    max_upload_mb: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let raw = fs::read_to_string("config.toml")?;
    let config: AppConfig = toml::from_str(&raw)?;

    println!("Server: {}:{}", config.server.host, config.server.port);
    println!("DB pool size: {}", config.database.pool_size);
    println!("Beta enabled: {}", config.feature_flags.enable_beta);

    Ok(())
}

Cargo.tomlの[dependencies]toml = "0.8"serde = { version = "1", features = ["derive"] }を追加すれば準備完了です。 serdeのderive マクロがすべてのフィールドマッピングを処理します。構造体フィールド名がsnake_caseを使いながら TOMLキーがkebab-caseを使う場合は、構造体レベルに#[serde(rename_all = "kebab-case")]を追加すると すべてが自動的にマッピングされます。

TOML対YAML対JSON — どれをいつ選ぶか

この質問はすべての新しいプロジェクトで出てきます。正直なまとめ:

  • TOML:人間が書き維持する設定ファイルに最適で、型付きの値が重要でネストが浅いから中程度の場合。得意分野:アプリケーション設定、ビルドマニフェスト、ツール設定。深いネストでは崩壊します — 4+レベルは繰り返しのセクションヘッダーで不便になります。
  • YAML:多くのオブジェクトリスト(Kubernetesマニフェスト、GitHub Actionsワークフロー)を持つ構造化データを書くときに最適。複数行文字列サポートはTOMLより本当に優れています。欠点:ホワイトスペース感度とYAML 1.1の型強制が実際のバグを生み出します。
  • JSON:マシン間のデータ交換、API、最も広いツールチェーンサポートが必要な場合に最適。人間が維持する設定には不向き — コメントなし、文字列エスケープが面倒。
簡単な経験則:開発者がテキストエディターで手動でファイルを編集する場合、 TOMLが通常最も快適な体験です。ツールによって生成されたり、異なる言語の12の異なるシステムで消費される場合は、 JSONの普遍性が勝ちます。YAMLは中間に位置します — 人間が書くがツールが大量に処理するDevOps設定に最適。

実際のpyproject.toml

すべてをまとめるために、Pythonライブラリの現実的なpyproject.tomlがあります — モダンなオープンソースプロジェクトで見つかるような種類のものです。フォーマットが一目でスキャンしやすいまま 多くの構造化情報を持っていることに注目してください:

toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "httpx-cache"
version = "1.2.0"
description = "Transparent HTTP caching layer for httpx"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
  { name = "Ada Lovelace", email = "[email protected]" },
]
keywords = ["http", "cache", "httpx", "async"]
classifiers = [
  "Development Status :: 4 - Beta",
  "Intended Audience :: Developers",
  "License :: OSI Approved :: MIT License",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
]
dependencies = [
  "httpx>=0.25.0",
  "anyio>=4.0.0",
]

[project.optional-dependencies]
redis = ["redis>=5.0.0"]
dev = [
  "pytest>=7.4.0",
  "pytest-asyncio>=0.23.0",
  "coverage[toml]>=7.3.0",
  "ruff>=0.1.0",
  "mypy>=1.7.0",
]

[project.urls]
Homepage = "https://github.com/example/httpx-cache"
Changelog = "https://github.com/example/httpx-cache/blob/main/CHANGELOG.md"

[tool.ruff]
line-length = 100
target-version = "py39"

[tool.ruff.lint]
select = ["E", "F", "I", "UP"]

[tool.mypy]
strict = true
python_version = "3.9"

[tool.coverage.run]
source = ["httpx_cache"]
branch = true

[tool.coverage.report]
fail_under = 90

これは本物の仕事をしている本物のTOMLです。各[tool.x]セクションは異なるツール — ruff、mypy、coverage — の別々の名前空間であり、すべてが互いに干渉せずに一つのファイルに住んでいます。 深いネストは不要で、すべてが一目でわかります。

まとめ

TOMLはその約束を果たします:読みやすく、曖昧さなく、クリーンに型付けされています。Rust、Python、または Goで新しいプロジェクトを開始する場合、TOMLは設定ファイルのデフォルトとして使う価値があります — 特に ソース管理にコミットされ複数の人が編集するファイルに。ホワイトスペース非感度だけでもYAMLと比べて救いになります。 ブラウザでTOMLファイルを直接扱うには、TOMLフォーマッターTOMLバリデーターが最も一般的なタスクを処理します。 JSONから既存プロジェクトを移行したり、TOMLをJSONベースのパイプラインにブリッジする必要がある場合は、 TOMLからJSONJSONからTOML コンバーターが対応しています。