rulemorph リファレンス
こちらの v1 リファレンスは非推奨となりました。
下記の v2 リファレンスを参照ください。
YAML定義に基づいてCSV/JSONをJSONに変換するツールのリファレンスです。
プレイグラウンド
目次
早見表
ルールファイル構造
version: 1
input:
format: csv | json
csv: { ... }
json: { ... }
output:
name: "TypeName"
record_when: { ... }
mappings:
- target: "..."
source: "..."
| フィールド | 必須 | 説明 |
|---|---|---|
| version | ○ | 固定値 1
|
| input | ○ | 入力形式と設定 |
| output | - | メタ情報(DTO生成時の型名など) |
| record_when | - | レコードフィルタ条件(input.*/context.*のみ参照可) |
| mappings | ○ | 変換ルール(上から順に評価) |
入力形式(CSV/JSON)
CSV設定
input:
format: csv
csv:
has_header: true
delimiter: ","
columns: # has_header=false 時に必須
- { name: "id", type: "string" }
- { name: "price", type: "float" }
| オプション | 既定値 | 説明 |
|---|---|---|
| has_header | true |
ヘッダー行の有無 |
| delimiter | "," |
区切り文字(1文字のみ) |
| columns | - |
has_header=false 時の列定義(必須) |
JSON設定
input:
format: json
json:
records_path: "data.items"
| オプション | 既定値 | 説明 |
|---|---|---|
| records_path | - | レコード配列へのドットパス(省略時はルート) |
注意:
records_pathがオブジェクトを指す場合は、単一レコードとして処理されます。
マッピングオプション
mappings:
- target: "user.id"
source: "id"
type: "string"
required: true
default: "unknown"
when: { ref: "input.active" }
| オプション | 必須 | 説明 |
|---|---|---|
| target | ○ | 出力先のドットパス |
| source | △ | 入力参照パス(value/exprと排他) |
| value | △ | リテラル値(source/exprと排他) |
| expr | △ | 式ツリー(source/valueと排他) |
| when | - | 条件式(falseでスキップ) |
| type | - | 型キャスト(string/int/float/bool) |
| required | - | 必須フラグ(既定false) |
| default | - |
missing時のデフォルト値 |
注意:
source/value/exprはいずれか1つを指定する必要があります。
演算子一覧
文字列操作
| op | 引数 | 説明 |
|---|---|---|
| concat | >=1 | 文字列連結 |
| to_string | 1 | 文字列化 |
| trim | 1 | 前後空白削除 |
| lowercase | 1 | 小文字化 |
| uppercase | 1 | 大文字化 |
| replace | 3-4 | 文字列置換 |
| split | 2 | 分割して配列化 |
| pad_start | 2-3 | 先頭埋め |
| pad_end | 2-3 | 末尾埋め |
数値演算
| op | 引数 | 説明 |
|---|---|---|
| + | >=2 | 加算 |
| - | 2 | 減算 |
| * | >=2 | 乗算 |
| / | 2 | 除算 |
| round | 1-2 | 四捨五入 |
| to_base | 2 | N進数変換 |
日付処理
| op | 引数 | 説明 |
|---|---|---|
| date_format | 2-4 | 日付フォーマット変換 |
| to_unixtime | 1-3 | Unix時間へ変換 |
論理演算
| op | 引数 | 説明 |
|---|---|---|
| and | >=2 | 論理AND(短絡評価) |
| or | >=2 | 論理OR(短絡評価) |
| not | 1 | 論理NOT |
比較演算
| op | 引数 | 説明 |
|---|---|---|
| == | 2 | 等価 |
| != | 2 | 非等価 |
| < | 2 | 小なり |
| <= | 2 | 以下 |
| > | 2 | 大なり |
| >= | 2 | 以上 |
| ~= | 2 | 正規表現マッチ |
検索・フォールバック
| op | 引数 | 説明 |
|---|---|---|
| coalesce | >=1 | 最初の非null/missing値 |
| lookup | 3-4 | 配列検索(全件) |
| lookup_first | 3-4 | 配列検索(先頭) |
JSON操作
| op | 引数 | 説明 |
|---|---|---|
| merge | >=2 | オブジェクトのシャローマージ |
| deep_merge | >=2 | オブジェクトのディープマージ |
| get | 2 | ドットパスで値を取得 |
| pick | 2 | 指定パスのみ抽出 |
| omit | 2 | 指定パスを除外 |
| keys | 1 | オブジェクトのキー配列 |
| values | 1 | オブジェクトの値配列 |
| entries | 1 |
[key, value]ペア配列 |
| len | 1 | 文字列/配列/オブジェクトの長さ |
| from_entries | 1-2 | エントリからオブジェクト構築 |
| object_flatten | 1 | ネストをフラット化 |
| object_unflatten | 1 | フラットをネストに復元 |
配列操作(変換)
| op | 引数 | 説明 |
|---|---|---|
| map | 2 | 各要素を変換 |
| filter | 2 | 条件に一致する要素のみ |
| flat_map | 2 | 変換後フラット化 |
| flatten | 1-2 | 配列のフラット化 |
配列操作(抽出)
| op | 引数 | 説明 |
|---|---|---|
| take | 2 | 先頭/末尾からN件取得 |
| drop | 2 | 先頭/末尾からN件除外 |
| slice | 3 | 範囲指定で取得 |
| chunk | 2 | N件ずつ分割 |
配列操作(結合)
| op | 引数 | 説明 |
|---|---|---|
| zip | 2 | 2配列をペア配列に |
| zip_with | 3 | 2配列を結合関数で結合 |
| unzip | 1 | ペア配列を2配列に分離 |
配列操作(グループ化)
| op | 引数 | 説明 |
|---|---|---|
| group_by | 2 | キーでグループ化(オブジェクト) |
| key_by | 2 | キーでインデックス化(1件のみ) |
| partition | 2 | 条件で2分割 |
配列操作(重複処理)
| op | 引数 | 説明 |
|---|---|---|
| unique | 1 | 重複除去(順序保持) |
| distinct_by | 2 | キー基準で重複除去 |
配列操作(ソート・検索)
| op | 引数 | 説明 |
|---|---|---|
| sort_by | 2 | キー基準で昇順ソート |
| find | 2 | 条件に一致する最初の要素 |
| find_index | 2 | 条件に一致する最初のインデックス |
| index_of | 2 | 値のインデックスを取得 |
| contains | 2 | 値が含まれるか判定 |
配列操作(集計)
| op | 引数 | 説明 |
|---|---|---|
| sum | 1 | 合計 |
| avg | 1 | 平均 |
| min | 1 | 最小値 |
| max | 1 | 最大値 |
| reduce | 2 | 畳み込み(初期値=最初の要素) |
| fold | 3 | 畳み込み(初期値指定) |
詳細解説
式(expr)の基本
マッピングでは値の指定に source/value/expr の3つの方法があります。
source と value の使い分け
# source: 入力フィールドを参照
- target: "id"
source: "id"
# value: リテラル値を設定
- target: "status"
value: "active"
- target: "count"
value: 100
- target: "enabled"
value: true
expr の2つの形式
expr は 参照形式 と 演算形式 をサポートします。
参照形式
入力データやコンテキストから値を参照します。
- target: "userId"
expr: { ref: "input.user_id" }
- target: "tenantId"
expr: { ref: "context.tenant_id" }
- target: "fullName"
expr: { ref: "out.name" }
演算形式
演算子を使用して値を変換・加工します。
- target: "fullName"
expr:
op: "concat"
args:
- { ref: "input.first_name" }
- " " # args 内ではリテラル値を直接使用可能
- { ref: "input.last_name" }
注意: リテラル値を設定するだけなら
valueを使います。exprは参照や演算が必要な場合に使用します。
args 内のリテラル
args 配列内では、リテラル値を直接指定できます。
expr:
op: "concat"
args:
- { ref: "input.name" } # 参照
- "@" # 文字列リテラル
- { ref: "input.domain" } # 参照
expr:
op: "+"
args:
- { ref: "input.price" } # 参照
- 100 # 数値リテラル
ネストした式
演算子の引数に別の演算子を指定できます。
# メールアドレスを小文字化してトリムする
- target: "email"
expr:
op: "lowercase"
args:
- op: "trim"
args: [ { ref: "input.email" } ]
chain式(パイプライン構文)
chain 構文を使うと、パイプライン風に式を連鎖できます。前の結果が次の操作の第1引数に自動注入されます。
基本構文
expr:
chain:
- { ref: "input.name" } # 初期値
- { op: "trim" } # args省略可(第1引数に前の結果が入る)
- { op: "replace", args: [ " ", "_", "all" ] } # 残りの引数を指定
- { op: "lowercase" }
上記は以下のネスト形式と同等です:
expr:
op: "lowercase"
args:
- op: "replace"
args:
- op: "trim"
args: [ { ref: "input.name" } ]
- " "
- "_"
- "all"
実用例:華氏から摂氏への変換
- target: "temp_c"
expr:
chain:
- { ref: "input.temp_f" }
- { op: "-", args: [ 32 ] }
- { op: "*", args: [ 5 ] }
- { op: "/", args: [ 9 ] }
- { op: "round", args: [ 2 ] }
# {"temp_f": 98.6} -> 37.0
実用例:文字列加工
- target: "slug"
expr:
chain:
- { ref: "input.title" }
- { op: "trim" }
- { op: "replace", args: [ " ", "-", "all" ] }
- { op: "lowercase" }
# {"title": " Hello World "} -> "hello-world"
配列操作との組み合わせ
- target: "even_doubled"
expr:
chain:
- { ref: "input.numbers" }
- op: "filter"
args:
- { op: "==", args: [ { op: "%", args: [ { ref: "item.value" }, 2 ] }, 0 ] }
- op: "map"
args:
- { op: "*", args: [ { ref: "item.value" }, 2 ] }
# {"numbers": [1,2,3,4]} -> [4, 8]
注意:
chain内の各ステップでargsを省略すると、前の結果が第1引数として自動挿入されます。追加の引数がある場合はargsに指定します。
参照(Reference)
参照は 名前空間 + ドットパス で指定します。
5つの名前空間
| 名前空間 | 説明 | 例 |
|---|---|---|
input.* |
入力レコード | input.user.name |
context.* |
実行時注入の外部データ | context.tenant_id |
out.* |
先行マッピングの結果 | out.fullName |
item.* |
配列操作時の現在要素 |
item.value, item.index
|
acc.* |
reduce/fold時のアキュムレータ | acc.value |
item / acc 名前空間(配列操作用)
配列操作演算子(map, filter, reduceなど)内で使用できる特別な名前空間です。
# item.value: 現在の配列要素
# item.index: 現在のインデックス(0始まり)
- target: "doubled"
expr:
op: "map"
args:
- { ref: "input.numbers" }
- { op: "*", args: [ { ref: "item.value" }, 2 ] }
# インデックスも参照可能
- target: "indexed"
expr:
op: "map"
args:
- { ref: "input.items" }
- { op: "concat", args: [ { ref: "item.index" }, ": ", { ref: "item.value" } ] }
# acc.value: reduce/fold でのアキュムレータ
- target: "total"
expr:
op: "reduce"
args:
- { ref: "input.numbers" }
- { op: "+", args: [ { ref: "acc.value" }, { ref: "item.value" } ] }
注意:
item.*とacc.*は配列操作演算子内でのみ有効です。それ以外の場所で使用するとバリデーションエラーになります。
source での省略ルール
source では 単一キー の場合のみ名前空間を省略できます。
# OK: 単一キーは省略可能
- target: "id"
source: "id" # input.id と同等
# NG: ドットパスは省略不可
- target: "name"
source: "user.name" # エラー!
# OK: ドットパスは明示的に指定
- target: "name"
source: "input.user.name"
expr では省略不可
expr 内の ref は常に名前空間を明示する必要があります。
# OK
expr: { ref: "input.id" }
# NG
expr: { ref: "id" } # エラー!
context の活用例(マスターデータ結合)
外部のマスターデータを context として注入し、lookup_first で結合できます。
# context: { "users": [{"id": 1, "name": "田中"}] }
- target: "userName"
expr:
op: "lookup_first"
args:
- { ref: "context.users" }
- "id"
- { ref: "input.user_id" }
- "name"
out の評価順序
out.* は上から順に評価されるため、先に定義されたマッピングの結果のみ 参照できます。
mappings:
- target: "firstName"
source: "first_name"
- target: "lastName"
source: "last_name"
# OK: 上で定義した値を参照
- target: "fullName"
expr:
op: "concat"
args:
- { ref: "out.firstName" }
- " "
- { ref: "out.lastName" }
# NG: 未定義の値は参照不可(バリデーションエラー)
# - target: "invalid"
# expr: { ref: "out.notYetDefined" }
ドットパス構文
ドットパスは以下の構文をサポートします。
| 構文 | 説明 | 例 |
|---|---|---|
field.nested |
ネストしたオブジェクト | input.user.profile |
array[N] |
配列の要素(0始まり) | input.items[0] |
field["key"] |
ドットを含むキー | input.user["profile.name"] |
配列インデックス
# 配列の要素にアクセス
source: "input.items[0].id"
# 多次元配列
source: "context.matrix[1][0]"
ドットを含むキー名
ブラケット引用を使用します。
# キー名に "." が含まれる場合
source: 'input.user["profile.name"]'
注意: 範囲外インデックスや存在しないパスは
missingとして扱われます。
missing vs null
rulemorph では「参照先が存在しない」状態と「値が null」の状態を区別します。
| 状態 | 意味 | 例 |
|---|---|---|
missing |
参照先が存在しない | キーがない、配列外参照 |
null |
参照先が存在し値が null | {"name": null} |
default の適用ルール
default は missing のときのみ 適用されます。null には適用されません。
# missing の場合: default が適用される
# null の場合: null のまま
- target: "status"
source: "status"
default: "unknown"
演算子での挙動の違い
| 演算子 | missing | null |
|---|---|---|
| concat | missing伝播 | エラー |
| coalesce | スキップ | スキップ |
| == | null として比較 | null として比較 |
| 数値演算 | missing伝播 | エラー |
required/default 挙動表
| required | default | 値がmissing | 値がnull |
|---|---|---|---|
| false | なし | フィールド生成されない | null |
| false | あり | default値 | null |
| true | なし | エラー | エラー |
| true | あり | default値 | エラー |
ポイント:
nullは「存在する」のでdefaultは適用されませんが、required=trueではエラーになります。
型キャスト変換ルール
type オプションで出力値の型を指定できます。
| 変換先 | 変換元 | 備考 |
|---|---|---|
string |
string/number/bool | そのまま文字列化 |
int |
number/数値文字列 |
1.0 → OK、1.1 → NG |
float |
number/数値文字列 | NaN/Infinity → NG |
bool |
bool/"true"/"false"
|
大文字小文字無視 |
# 文字列を整数に変換
- target: "count"
source: "count"
type: "int"
# 数値を文字列に変換
- target: "id"
source: "id"
type: "string"
# 文字列をboolに変換("true"/"false" のみ)
- target: "enabled"
source: "enabled"
type: "bool"
record_when レコードフィルタリング
record_when はルートレベルで指定し、マッピング処理の前にレコード単位でフィルタリングします。条件が false のレコードは出力から除外されます。
基本構文
version: 1
input:
format: json
json: {}
record_when:
op: "=="
args:
- { ref: "input.status" }
- "active"
mappings:
- target: "id"
source: "id"
# status が "active" のレコードのみ変換される
正規表現によるフィルタリング
record_when:
op: "~="
args:
- { ref: "input.name" }
- "^b.{3,}$"
# 名前が "b" で始まり4文字以上のレコードのみ
context との組み合わせ
# context: { "target_tier": "premium" }
record_when:
op: "=="
args:
- { ref: "input.tier" }
- { ref: "context.target_tier" }
# context で指定した tier のレコードのみ変換
複合条件
record_when:
op: "and"
args:
- { op: ">=", args: [ { ref: "input.age" }, 18 ] }
- { op: "~=", args: [ { ref: "input.email" }, "@company\\.com$" ] }
# 18歳以上かつ社内メールアドレスのレコードのみ
注意:
record_whenではinput.*とcontext.*のみ参照可能です。out.*は参照できません(マッピング処理前のため)。
when 条件分岐
when オプションを使うと、条件に応じてマッピングをスキップできます。
基本: boolフィールド参照
- target: "name"
source: "name"
when: { ref: "input.active" }
値による分岐
- target: "premiumBadge"
value: true
when:
op: "=="
args: [ { ref: "input.tier" }, "premium" ]
論理演算の組み合わせ
- target: "adultVerified"
value: true
when:
op: "and"
args:
- { op: ">=", args: [ { ref: "input.age" }, 18 ] }
- { ref: "input.verified" }
正規表現によるパターンマッチ
- target: "isInternalEmail"
value: true
when:
op: "~="
args: [ { ref: "input.email" }, ".+@company\\.com$" ]
注意:
whenがfalseまたは評価エラーの場合、そのマッピングはスキップされます(エラーは warning として出力)。
演算子詳細
文字列操作
concat
全引数を文字列化して連結します。
expr:
op: "concat"
args:
- { ref: "input.first_name" }
- " "
- { ref: "input.last_name" }
# {"first_name": "太郎", "last_name": "山田"} -> "太郎 山田"
missing→ missing伝播、null→ エラー
to_string
値を文字列に変換します。
expr:
op: "to_string"
args: [ { ref: "input.age" } ]
# {"age": 25} -> "25"
trim
前後の空白を削除します。
expr:
op: "trim"
args: [ { ref: "input.name" } ]
# {"name": " 田中 "} -> "田中"
lowercase
小文字に変換します。
expr:
op: "lowercase"
args: [ { ref: "input.code" } ]
# {"code": "ABC"} -> "abc"
uppercase
大文字に変換します。
expr:
op: "uppercase"
args: [ { ref: "input.code" } ]
# {"code": "abc"} -> "ABC"
replace
文字列を置換します。4番目の引数で置換モードを指定できます。
# デフォルト(先頭一致のみ)
expr:
op: "replace"
args: [ { ref: "input.text" }, "abc", "XYZ" ]
# "abc-123-abc" -> "XYZ-123-abc"
# all(全置換)
expr:
op: "replace"
args: [ { ref: "input.text" }, "abc", "XYZ", "all" ]
# "abc-123-abc" -> "XYZ-123-XYZ"
# regex(正規表現、先頭一致)
expr:
op: "replace"
args: [ { ref: "input.text" }, "\\d+", "NUM", "regex" ]
# "abc-123-456" -> "abc-NUM-456"
# regex_all(正規表現、全置換)
expr:
op: "replace"
args: [ { ref: "input.text" }, "\\d", "_", "regex_all" ]
# "abc-123" -> "abc-___"
split
区切り文字で分割して配列を返します。
expr:
op: "split"
args: [ { ref: "input.tags" }, "," ]
# {"tags": "a,b,c"} -> ["a", "b", "c"]
pad_start
指定長になるまで先頭を埋めます。
expr:
op: "pad_start"
args: [ { ref: "input.code" }, 5, "0" ]
# {"code": "42"} -> "00042"
pad_end
指定長になるまで末尾を埋めます。
expr:
op: "pad_end"
args: [ { ref: "input.name" }, 10, "_" ]
# {"name": "ABC"} -> "ABC_______"
数値演算
加算
expr:
op: "+"
args: [ 1, "2", 3 ]
# -> 6(数値文字列も可)
減算
expr:
op: "-"
args: [ 10, 4 ]
# -> 6
乗算
expr:
op: "*"
args: [ 2, 3, 4 ]
# -> 24
除算
expr:
op: "/"
args: [ 9, 2 ]
# -> 4.5
round
四捨五入します。第2引数で小数桁数を指定できます。
# 整数に丸める
expr:
op: "round"
args: [ 12.5 ]
# -> 13
# 小数第2位まで
expr:
op: "round"
args: [ 12.345, 2 ]
# -> 12.35
to_base
整数をN進数の文字列に変換します(2-36進数対応)。
# 16進数変換
expr:
op: "to_base"
args: [ { ref: "input.color" }, 16 ]
# {"color": 255} -> "ff"
# 2進数変換
expr:
op: "to_base"
args: [ 10, 2 ]
# -> "1010"
複合計算例(華氏→摂氏変換)
expr:
op: "round"
args:
- op: "*"
args:
- { op: "-", args: [ { ref: "input.fahrenheit" }, 32 ] }
- { op: "/", args: [ 5, 9 ] }
- 2
# {"fahrenheit": 98.6} -> 37.0
日付処理
date_format
日時文字列を別のフォーマットに変換します。
# 基本
expr:
op: "date_format"
args: [ { ref: "input.date" }, "%Y/%m/%d" ]
# {"date": "2024-01-02"} -> "2024/01/02"
# タイムゾーン指定
expr:
op: "date_format"
args:
- { ref: "input.datetime" }
- "%Y-%m-%d %H:%M:%S"
- "UTC" # input_format(省略可)
- "+09:00" # 出力タイムゾーン
対応フォーマット:
- ISO 8601:
2024-01-02T15:04:05Z - RFC 2822/3339
-
YYYY-MM-DD/YYYY/MM/DD
to_unixtime
日時文字列をUnix時間に変換します。
# 秒単位
expr:
op: "to_unixtime"
args: [ "1970-01-01T00:00:01Z" ]
# -> 1
# ミリ秒単位
expr:
op: "to_unixtime"
args: [ { ref: "input.timestamp" }, "ms" ]
論理演算
and
全ての引数が true のとき true を返します(短絡評価)。
expr:
op: "and"
args:
- { op: ">=", args: [ { ref: "input.age" }, 18 ] }
- { ref: "input.verified" }
# 年齢18以上 かつ verified=true のときのみ true
or
いずれかの引数が true のとき true を返します(短絡評価)。
expr:
op: "or"
args:
- { ref: "input.is_admin" }
- { ref: "input.is_owner" }
# admin または owner なら true
not
論理否定します。
expr:
op: "not"
args: [ { ref: "input.disabled" } ]
# {"disabled": false} -> true
比較演算
等価比較
# ==: 等価
expr:
op: "=="
args: [ { ref: "input.status" }, "active" ]
# !=: 非等価
expr:
op: "!="
args: [ { ref: "input.status" }, "deleted" ]
missingはnullとして扱われます。null == nullはtrueです。
数値比較
# <, <=, >, >=
expr:
op: ">="
args: [ { ref: "input.score" }, 60 ]
# {"score": 75} -> true
数値または数値文字列のみ対応。
nullや非数値はエラーになります。
正規表現マッチ
expr:
op: "~="
args: [ { ref: "input.email" }, ".+@example\\.com$" ]
# {"email": "test@example.com"} -> true
Rust regex 準拠の正規表現を使用します。
検索・フォールバック
coalesce
最初の「missing でも null でもない」値を返します。
# ニックネームがなければ名前、なければ "unknown"
expr:
op: "coalesce"
args:
- { ref: "input.nickname" }
- { ref: "input.name" }
- "unknown"
lookup
配列を検索し、マッチした要素を 配列 で返します。
# context.tags から id が一致するものを全て取得
expr:
op: "lookup"
args:
- { ref: "context.tags" } # 検索対象配列
- "id" # マッチキー
- { ref: "input.tag_id" } # マッチ値
- "value" # 出力パス(省略可)
# context.tags = [{"id":"p1","value":"hot"},{"id":"p1","value":"sale"}]
# input.tag_id = "p1"
# -> ["hot", "sale"]
lookup_first
配列を検索し、最初にマッチした要素を返します。
# ユーザーIDから名前を取得
expr:
op: "lookup_first"
args:
- { ref: "context.users" }
- "id"
- { ref: "input.user_id" }
- "name"
# context.users = [{"id": 1, "name": "田中"}]
# input.user_id = 1
# -> "田中"
lookup + coalesce の組み合わせ
マッチしない場合のフォールバック値を設定できます。
expr:
op: "coalesce"
args:
- op: "lookup_first"
args:
- { ref: "context.users" }
- "id"
- { ref: "input.user_id" }
- "name"
- "不明なユーザー"
JSON操作
merge
複数のオブジェクトをシャローマージします。後のオブジェクトが優先されます。
expr:
op: "merge"
args:
- { ref: "input.base" }
- { ref: "input.override" }
# {"base": {"a": 1, "b": 2}, "override": {"b": 3, "c": 4}}
# -> {"a": 1, "b": 3, "c": 4}
deep_merge
複数のオブジェクトをディープマージします。ネストしたオブジェクトも再帰的にマージされます。
expr:
op: "deep_merge"
args:
- { ref: "input.base" }
- { ref: "input.override" }
# {"base": {"a": {"x": 1}}, "override": {"a": {"y": 2}}}
# -> {"a": {"x": 1, "y": 2}}
get
ドットパスで値を取得します。
expr:
op: "get"
args:
- { ref: "input.obj" }
- "user.profile.name"
# {"obj": {"user": {"profile": {"name": "田中"}}}}
# -> "田中"
pick
指定パスのみを抽出した新しいオブジェクトを返します。
expr:
op: "pick"
args:
- { ref: "input.obj" }
- ["id", "profile.name", "items[0].id"]
# {"obj": {"id": 1, "profile": {"name": "田中", "age": 30}, "items": [{"id": "a"}]}}
# -> {"id": 1, "profile": {"name": "田中"}, "items": [{"id": "a"}]}
omit
指定パスを除外した新しいオブジェクトを返します。
expr:
op: "omit"
args:
- { ref: "input.obj" }
- ["password", "internal.secret"]
# {"obj": {"id": 1, "password": "xxx", "internal": {"secret": "yyy", "data": "zzz"}}}
# -> {"id": 1, "internal": {"data": "zzz"}}
keys
オブジェクトのキーを配列で返します。
expr:
op: "keys"
args: [ { ref: "input.map" } ]
# {"map": {"a": 1, "b": 2}}
# -> ["a", "b"]
values
オブジェクトの値を配列で返します。
expr:
op: "values"
args: [ { ref: "input.map" } ]
# {"map": {"a": 1, "b": 2}}
# -> [1, 2]
entries
オブジェクトを [key, value] ペアの配列に変換します。
expr:
op: "entries"
args: [ { ref: "input.map" } ]
# {"map": {"a": 1, "b": 2}}
# -> [["a", 1], ["b", 2]]
len
文字列/配列/オブジェクトの長さを返します。
- 文字列: Unicode文字数(バイト数ではない)
- 配列: 要素数
- オブジェクト: キー数
# 配列の長さ
expr:
op: "len"
args: [ { ref: "input.array" } ]
# {"array": [1, 2, 3]} -> 3
# 文字列の長さ(Unicode文字単位)
expr:
op: "len"
args: [ { ref: "input.text" } ]
# {"text": "こんにちは"} -> 5
# オブジェクトのキー数
expr:
op: "len"
args: [ { ref: "input.obj" } ]
# {"obj": {"a": 1, "b": 2}} -> 2
# chain構文でも使用可能
expr:
chain:
- { ref: "input.items" }
- { op: "len" }
missing→ missing伝播、null→ エラー
from_entries
エントリからオブジェクトを構築します。1引数と2引数の2つのモードがあります。
1引数モード: 配列またはオブジェクトを受け取る
- オブジェクト: そのまま返す
- 配列: 各要素を
[key, value]または{key, value}形式として処理
# [key, value]形式の配列
expr:
op: "from_entries"
args: [ { ref: "input.pairs" } ]
# {"pairs": [["a", 1], ["b", 2]]} -> {"a": 1, "b": 2}
# {key, value}形式の配列
expr:
op: "from_entries"
args: [ { ref: "input.entries" } ]
# {"entries": [{"key": "x", "value": 10}, {"key": "y", "value": 20}]}
# -> {"x": 10, "y": 20}
# オブジェクトはそのまま返す
expr:
op: "from_entries"
args: [ { ref: "input.obj" } ]
# {"obj": {"a": 1}} -> {"a": 1}
2引数モード: キーと値を別々に指定
expr:
op: "from_entries"
args:
- { ref: "input.key" }
- { ref: "input.value" }
# {"key": "name", "value": "田中"} -> {"name": "田中"}
# 動的なキー生成にも使用可能
expr:
op: "from_entries"
args:
- "status"
- { ref: "input.code" }
# {"code": 200} -> {"status": 200}
キーは
string/number/boolのみ(重複時は後勝ち)
missing→ missing伝播、キーがnull→ エラー、値はnullでもOK
object_flatten
ネストしたオブジェクトをドットパスキーでフラット化します。
expr:
op: "object_flatten"
args: [ { ref: "input.obj" } ]
# {"obj": {"user": {"name": "田中", "profile": {"age": 30}}}}
# -> {"user.name": "田中", "user.profile.age": 30}
object_unflatten
ドットパスキーのオブジェクトをネスト構造に復元します。
expr:
op: "object_unflatten"
args: [ { ref: "input.flat" } ]
# {"flat": {"user.name": "田中", "user.profile.age": 30}}
# -> {"user": {"name": "田中", "profile": {"age": 30}}}
配列操作(変換)
map
各要素に対して式を適用し、結果の配列を返します。
expr:
op: "map"
args:
- { ref: "input.numbers" }
- { op: "*", args: [ { ref: "item.value" }, 2 ] }
# {"numbers": [1, 2, 3]} -> [2, 4, 6]
# インデックスも使用可能
expr:
op: "map"
args:
- { ref: "input.items" }
- { ref: "item.index" }
# {"items": ["a", "b", "c"]} -> [0, 1, 2]
filter
条件に一致する要素のみを含む配列を返します。
expr:
op: "filter"
args:
- { ref: "input.numbers" }
- { op: ">", args: [ { ref: "item.value" }, 2 ] }
# {"numbers": [1, 2, 3, 4]} -> [3, 4]
flat_map
各要素を変換後、結果をフラット化します。
expr:
op: "flat_map"
args:
- { ref: "input.nested" }
- { ref: "item.value" }
# {"nested": [[1, 2], [3, 4]]} -> [1, 2, 3, 4]
flatten
配列を指定の深さまでフラット化します。
# 1階層(デフォルト)
expr:
op: "flatten"
args: [ { ref: "input.nested" } ]
# [[1, 2], [3, 4]] -> [1, 2, 3, 4]
# 2階層
expr:
op: "flatten"
args: [ { ref: "input.nested" }, 2 ]
# [[[1, 2]], [[3, 4]]] -> [1, 2, 3, 4]
配列操作(抽出)
take
先頭または末尾からN件取得します。
# 先頭2件
expr:
op: "take"
args: [ { ref: "input.items" }, 2 ]
# [1, 2, 3, 4, 5] -> [1, 2]
# 末尾2件(負数)
expr:
op: "take"
args: [ { ref: "input.items" }, -2 ]
# [1, 2, 3, 4, 5] -> [4, 5]
drop
先頭または末尾からN件除外します。
# 先頭2件を除外
expr:
op: "drop"
args: [ { ref: "input.items" }, 2 ]
# [1, 2, 3, 4, 5] -> [3, 4, 5]
# 末尾2件を除外(負数)
expr:
op: "drop"
args: [ { ref: "input.items" }, -2 ]
# [1, 2, 3, 4, 5] -> [1, 2, 3]
slice
範囲指定で要素を取得します(負数は末尾からの位置)。
expr:
op: "slice"
args: [ { ref: "input.items" }, 1, -1 ]
# [1, 2, 3, 4, 5] -> [2, 3, 4](インデックス1から末尾-1まで)
chunk
配列をN件ずつの配列に分割します。
expr:
op: "chunk"
args: [ { ref: "input.items" }, 2 ]
# [1, 2, 3, 4, 5] -> [[1, 2], [3, 4], [5]]
配列操作(結合)
zip
2つの配列をペアの配列に結合します。
expr:
op: "zip"
args:
- { ref: "input.keys" }
- { ref: "input.values" }
# {"keys": ["a", "b"], "values": [1, 2]}
# -> [["a", 1], ["b", 2]]
zip_with
2つの配列を結合関数で処理します。
expr:
op: "zip_with"
args:
- { ref: "input.nums" }
- { ref: "input.strs" }
- op: "concat"
args:
- { op: "to_string", args: [ { ref: "item.value[0]" } ] }
- "-"
- { ref: "item.value[1]" }
# {"nums": [1, 2], "strs": ["a", "b"]}
# -> ["1-a", "2-b"]
unzip
ペア配列を2つの配列に分離します。
expr:
op: "unzip"
args: [ { ref: "input.pairs" } ]
# {"pairs": [["a", 1], ["b", 2]]}
# -> [["a", "b"], [1, 2]]
配列操作(グループ化)
group_by
キー式でグループ化し、オブジェクトを返します。
expr:
op: "group_by"
args:
- { ref: "input.items" }
- { ref: "item.value.category" }
# {"items": [{"id": 1, "category": "A"}, {"id": 2, "category": "B"}, {"id": 3, "category": "A"}]}
# -> {"A": [{"id": 1, ...}, {"id": 3, ...}], "B": [{"id": 2, ...}]}
key_by
キー式でインデックス化し、オブジェクトを返します(重複時は最後の要素)。
expr:
op: "key_by"
args:
- { ref: "input.items" }
- { ref: "item.value.id" }
# {"items": [{"id": "a", "name": "A"}, {"id": "b", "name": "B"}]}
# -> {"a": {"id": "a", "name": "A"}, "b": {"id": "b", "name": "B"}}
partition
条件で配列を2分割します。[true配列, false配列] を返します。
expr:
op: "partition"
args:
- { ref: "input.numbers" }
- { op: ">", args: [ { ref: "item.value" }, 2 ] }
# {"numbers": [1, 2, 3, 4]}
# -> [[3, 4], [1, 2]]
配列操作(重複処理)
unique
配列から重複を除去します(順序保持)。
expr:
op: "unique"
args: [ { ref: "input.tags" } ]
# {"tags": ["a", "b", "a", "c", "b"]}
# -> ["a", "b", "c"]
distinct_by
キー式基準で重複を除去します(最初の要素を保持)。
expr:
op: "distinct_by"
args:
- { ref: "input.items" }
- { ref: "item.value.id" }
# {"items": [{"id": 1, "v": "a"}, {"id": 2, "v": "b"}, {"id": 1, "v": "c"}]}
# -> [{"id": 1, "v": "a"}, {"id": 2, "v": "b"}]
配列操作(ソート・検索)
sort_by
キー式基準で昇順ソートします。
expr:
op: "sort_by"
args:
- { ref: "input.items" }
- { ref: "item.value.score" }
# {"items": [{"score": 3}, {"score": 1}, {"score": 2}]}
# -> [{"score": 1}, {"score": 2}, {"score": 3}]
find
条件に一致する最初の要素を返します(見つからなければ null)。
expr:
op: "find"
args:
- { ref: "input.items" }
- { op: ">", args: [ { ref: "item.value" }, 2 ] }
# {"items": [1, 2, 3, 4]} -> 3
find_index
条件に一致する最初の要素のインデックスを返します(見つからなければ -1)。
expr:
op: "find_index"
args:
- { ref: "input.items" }
- { op: ">", args: [ { ref: "item.value" }, 2 ] }
# {"items": [1, 2, 3, 4]} -> 2
index_of
値のインデックスを返します(見つからなければ -1)。
expr:
op: "index_of"
args: [ { ref: "input.items" }, 3 ]
# {"items": [1, 2, 3, 4]} -> 2
contains
配列に値が含まれるかを判定します。
expr:
op: "contains"
args: [ { ref: "input.tags" }, "premium" ]
# {"tags": ["basic", "premium"]} -> true
配列操作(集計)
sum
数値配列の合計を返します。
expr:
op: "sum"
args: [ { ref: "input.numbers" } ]
# {"numbers": [1, 2, 3, 4]} -> 10
avg
数値配列の平均を返します。
expr:
op: "avg"
args: [ { ref: "input.numbers" } ]
# {"numbers": [1, 2, 3, 4]} -> 2.5
min
数値配列の最小値を返します。
expr:
op: "min"
args: [ { ref: "input.numbers" } ]
# {"numbers": [3, 1, 4, 1, 5]} -> 1
max
数値配列の最大値を返します。
expr:
op: "max"
args: [ { ref: "input.numbers" } ]
# {"numbers": [3, 1, 4, 1, 5]} -> 5
reduce
配列を畳み込みます。初期値は最初の要素です。
expr:
op: "reduce"
args:
- { ref: "input.numbers" }
- { op: "+", args: [ { ref: "acc.value" }, { ref: "item.value" } ] }
# {"numbers": [1, 2, 3, 4]} -> 10
# 評価: ((1 + 2) + 3) + 4
fold
初期値を指定して配列を畳み込みます。
expr:
op: "fold"
args:
- { ref: "input.numbers" }
- 100
- { op: "+", args: [ { ref: "acc.value" }, { ref: "item.value" } ] }
# {"numbers": [1, 2, 3, 4]} -> 110
# 評価: (((100 + 1) + 2) + 3) + 4
DTO自動生成
rulemorph はルールファイルから7言語のDTO定義を自動生成できます。
対応言語
- Rust
- TypeScript
- Python
- Go
- Java
- Kotlin
- Swift
TypeScript 出力例
ルールファイル:
version: 1
input:
format: json
output:
name: "User"
mappings:
- target: "id"
source: "id"
type: "string"
required: true
- target: "profile.name"
source: "name"
- target: "profile.age"
source: "age"
type: "int"
- target: "active"
source: "active"
type: "bool"
required: true
生成されるTypeScript:
export interface User {
id: string;
profile?: UserProfile;
active: boolean;
}
export interface UserProfile {
name?: string;
age?: number;
}
required: trueのフィールドは必須プロパティに、それ以外はオプショナル(?)になります。
完成例
CSV → JSON 変換
シンプルなCSVデータを構造化されたJSONに変換する例です。
入力CSV
id,name,price,category
1,Apple,150,fruit
2,Carrot,80,vegetable
ルールファイル
version: 1
input:
format: csv
csv:
has_header: true
mappings:
- target: "product.id"
source: "id"
type: "int"
- target: "product.name"
source: "name"
- target: "product.price"
source: "price"
type: "float"
- target: "product.category"
expr:
op: "uppercase"
args: [ { ref: "input.category" } ]
出力JSON
[
{"product": {"id": 1, "name": "Apple", "price": 150.0, "category": "FRUIT"}},
{"product": {"id": 2, "name": "Carrot", "price": 80.0, "category": "VEGETABLE"}}
]
JSON ネスト変換
深いネスト構造からフラットな構造に変換する例です。
入力JSON(APIレスポンス想定)
{
"status": "ok",
"data": {
"users": [
{"id": 1, "profile": {"name": "田中", "email": "tanaka@example.com"}},
{"id": 2, "profile": {"name": "鈴木", "email": "suzuki@example.com"}}
]
}
}
ルールファイル
version: 1
input:
format: json
json:
records_path: "data.users"
mappings:
- target: "userId"
source: "id"
type: "int"
- target: "name"
source: "input.profile.name"
- target: "email"
expr:
op: "lowercase"
args: [ { ref: "input.profile.email" } ]
出力JSON
[
{"userId": 1, "name": "田中", "email": "tanaka@example.com"},
{"userId": 2, "name": "鈴木", "email": "suzuki@example.com"}
]
context 結合(マスターデータ参照)
外部のマスターデータと結合する例です。
入力JSON
[
{"order_id": "ORD001", "user_id": 1, "amount": 1500},
{"order_id": "ORD002", "user_id": 2, "amount": 3200}
]
コンテキスト(マスターデータ)
{
"users": [
{"id": 1, "name": "田中太郎", "tier": "gold"},
{"id": 2, "name": "鈴木花子", "tier": "silver"}
]
}
ルールファイル
version: 1
input:
format: json
json: {}
mappings:
- target: "orderId"
source: "order_id"
- target: "userName"
expr:
op: "lookup_first"
args:
- { ref: "context.users" }
- "id"
- { ref: "input.user_id" }
- "name"
- target: "userTier"
expr:
op: "coalesce"
args:
- op: "lookup_first"
args:
- { ref: "context.users" }
- "id"
- { ref: "input.user_id" }
- "tier"
- "standard"
- target: "amount"
source: "amount"
type: "int"
出力JSON
[
{"orderId": "ORD001", "userName": "田中太郎", "userTier": "gold", "amount": 1500},
{"orderId": "ORD002", "userName": "鈴木花子", "userTier": "silver", "amount": 3200}
]
トラブルシューティング
よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
| missing required field |
required=true で値が missing または null
|
default を追加、または required=false に変更 |
| type cast failed | 型変換不可(例: "abc" を int に変換) |
入力値を確認、型指定を見直し |
| forward out reference | 未定義の out.* を参照 |
マッピングの順序を確認(上から順に評価) |
| invalid namespace | 不正な名前空間(input/context/out 以外) |
名前空間を確認 |
| duplicate target | 同じ target が複数のマッピングで定義 |
target の重複を解消 |
| null in concat |
concat の引数に null 値 |
coalesce で null をフォールバック |
| division by zero | 除算の分母が 0 | 入力値を確認 |
デバッグのヒント
- 段階的に構築: 複雑な式は一度に書かず、シンプルなマッピングから始めて徐々に追加
-
missing vs null: 意図しない
nullはcoalesceでフォールバック -
型変換エラー: 入力データの型を確認、必要なら
to_stringで文字列化してから処理 - out 参照: マッピングの順序を確認、参照先が先に定義されているか確認