🔄

変換ロジックをYAMLに追い出したら楽になった

に公開

変換ロジックをYAMLに追い出したら楽になった

データ変換のロジックって、気づくとコードのあちこちに散らばりますよね。

「このフィールドは文字列結合して…」「この値は条件によって変えて…」みたいな処理が、いつの間にかビジネスロジックと混ざってしまう。

そして「外部データの形式が変わりました」と言われると、こうなる:

フィールド名が変わっただけなのに、このサイクルを回すのはつらい。

そこで、変換ルールをYAMLファイルに切り出せるツールを作りました。

プレイグラウンド: すぐにお試しいただけます。
https://playground.rulemorph.com/
GitHub:
https://github.com/vinhphatfsg/rulemorph

何が楽になるのか

変換ロジックをYAMLに外出しすると、こんなメリットがあります:

従来 rulemorph
変換ロジックがコードに散在 YAMLに集約
変更のたびにビルド・デプロイ ファイル差し替えで完了
  1. 変換ルールが一箇所にまとまる - コード内に散らばらない
  2. ルールがそのまま仕様書になる - YAMLは人間が読める
  3. ルール変更にビルドがいらない - 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:
https://github.com/vinhphatfsg/rulemorph

フィードバックやコントリビューションお待ちしています。

Discussion