🔄
変換ロジックをYAMLに追い出したら楽になった
変換ロジックをYAMLに追い出したら楽になった
データ変換のロジックって、気づくとコードのあちこちに散らばりますよね。
「このフィールドは文字列結合して…」「この値は条件によって変えて…」みたいな処理が、いつの間にかビジネスロジックと混ざってしまう。
そして「外部データの形式が変わりました」と言われると、こうなる:
フィールド名が変わっただけなのに、このサイクルを回すのはつらい。
そこで、変換ルールをYAMLファイルに切り出せるツールを作りました。
プレイグラウンド: すぐにお試しいただけます。 GitHub:
何が楽になるのか
変換ロジックをYAMLに外出しすると、こんなメリットがあります:
| 従来 | rulemorph |
|---|---|
| 変換ロジックがコードに散在 | YAMLに集約 |
| 変更のたびにビルド・デプロイ | ファイル差し替えで完了 |
- 変換ルールが一箇所にまとまる - コード内に散らばらない
- ルールがそのまま仕様書になる - YAMLは人間が読める
- ルール変更にビルドがいらない - YAMLファイルを差し替えるだけ
使い方
インストール
# Homebrew(推奨)
brew install vinhphatfsg/tap/rulemorph
# またはCargo
cargo install rulemorph_cli
基本の流れ
入力データがあって:
{ "users": [{ "user_id": 1, "full_name": "Alice", "username": "alice" }] }
変換ルールをYAMLで書いて:
version: 1
input:
format: json
json:
records_path: "users"
mappings:
- target: "id"
source: "input.user_id"
- target: "name"
source: "input.full_name"
- target: "email"
expr:
concat:
- { ref: "input.username" }
- "@example.com"
コマンドを実行すると:
rulemorph transform -r rules.yaml -i input.json
変換結果が得られます:
[{ "id": 1, "name": "Alice", "email": "alice@example.com" }]
YAMLで書ける変換ルール
フィールドのコピー
# input: { "user_id": 123 }
# output: { "id": 123 }
- target: "id"
source: "input.user_id"
固定値の設定
# input: { "name": "Alice" }
# output: { "name": "Alice", "version": "1.0" }
- target: "name"
source: "input.name"
- target: "version"
value: "1.0"
文字列の結合
# input: { "first": "太郎", "last": "山田" }
# output: { "name": "山田 太郎" }
- target: "name"
expr:
concat: [{ ref: "input.last" }, " ", { ref: "input.first" }]
条件付きマッピング
# input: { "score": 80 }
# output: { "status": "pass" }
# input: { "score": 50 }
# output: { } (whenがfalseなので出力されない)
- target: "status"
value: "pass"
when:
">=": [{ ref: "input.score" }, 60]
計算と再利用
# input: { "price": 1000, "quantity": 3 }
# output: { "subtotal": 3000, "tax": 300, "total": 3300 }
- target: "subtotal"
expr:
"*": [{ ref: "input.price" }, { ref: "input.quantity" }]
- target: "tax"
expr:
"*": [{ ref: "out.subtotal" }, 0.1]
- target: "total"
expr:
"+": [{ ref: "out.subtotal" }, { ref: "out.tax" }]
チェーン(パイプライン風)
# input: { "name": " ALICE " }
# output: { "name": "alice" }
- target: "name"
expr:
chain:
- { ref: "input.name" }
- { op: trim }
- { op: lowercase }
配列のフィルタとマップ
# input: { "items": [{ "name": "A", "price": 500 }, { "name": "B", "price": 1500 }] }
# output: { "expensive_items": ["B"] }
- target: "expensive_items"
expr:
chain:
- { ref: "input.items" }
- op: filter
args:
- { ">": [{ ref: "item.price" }, 1000] }
- op: map
args:
- ref: "item.name"
日付フォーマット
# input: { "date": "2024-01-15" }
# output: { "date_jp": "2024年01月15日" }
- target: "date_jp"
expr:
date_format:
- { ref: "input.date" }
- "%Y年%m月%d日"
- "%Y-%m-%d"
ルックアップ
# input: { "category_id": 2 }
# context: { "categories": [{ "id": 1, "name": "Food" }, { "id": 2, "name": "Book" }] }
# output: { "category": "Book" }
- target: "category"
expr:
lookup:
- { ref: "context.categories" }
- "id"
- { ref: "input.category_id" }
- "name"
CSVも対応
version: 1
input:
format: csv
csv:
has_header: true
mappings:
- target: "amount"
source: "input.金額"
type: float
DTO自動生成
ルールから型定義を生成できます:
rulemorph generate -r rules.yaml -l typescript
export interface Record {
id: number;
name: string;
email: string;
}
対応言語:Rust, TypeScript, Python, Go, Java, Kotlin, Swift
MCP対応
Claude Desktopと連携できます:
claude mcp add rulemorph -- rulemorph-mcp
ライブラリとして使う
Rustプロジェクトに組み込むこともできます:
use rulemorph::{parse_rule_file, transform};
let rule = parse_rule_file(&std::fs::read_to_string("rules.yaml")?)?;
let output = transform(&rule, &std::fs::read_to_string("input.json")?, None)?;
まとめ
変換ロジックをYAMLに追い出すと:
- ロジックが一箇所にまとまって見通しが良くなる
- YAMLがそのまま仕様書として機能する
- ルール変更がファイル差し替えで済む
GitHub:
フィードバックやコントリビューションお待ちしています。
Discussion