rulemorph リファレンス (v2)
YAML定義に基づいてCSV/JSONをJSONに変換するツールのリファレンスです。
▼ プレイグラウンド
目次
早見表
ルールファイル構造
version: 2
input:
format: csv | json
csv: { ... }
json: { ... }
output:
name: "TypeName"
record_when:
gt: ["@input.score", 10]
mappings:
- target: "..."
source: "..."
| フィールド | 必須 | 説明 |
|---|---|---|
| version | ○ | 固定値 2
|
| 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:
eq: ["@input.active", true]
| オプション | 必須 | 説明 |
|---|---|---|
| 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 | 0 | 文字列化 |
| trim | 0 | 前後空白削除 |
| lowercase | 0 | 小文字化 |
| uppercase | 0 | 大文字化 |
| replace | 2-3 | 文字列置換 |
| split | 1 | 分割して配列化 |
| pad_start | 1-2 | 先頭埋め |
| pad_end | 1-2 | 末尾埋め |
数値演算
| op | 引数 | 説明 |
|---|---|---|
| + / add | >=1 | 加算 |
| - | >=1 | 減算 |
| * / multiply | >=1 | 乗算 |
| / | >=1 | 除算 |
| round | 0-1 | 四捨五入 |
| to_base | 1 | N進数変換 |
日付処理
| op | 引数 | 説明 |
|---|---|---|
| date_format | 1-3 | 日付フォーマット変換 |
| to_unixtime | 0-2 | Unix時間へ変換 |
論理演算
| op | 引数 | 説明 |
|---|---|---|
| and | >=1 | 論理AND |
| or | >=1 | 論理OR |
| not | 0 | 論理NOT |
比較演算
| op | 引数 | 説明 |
|---|---|---|
| == | 1 | 等価 |
| != | 1 | 非等価 |
| < | 1 | 小なり |
| <= | 1 | 以下 |
| > | 1 | 大なり |
| >= | 1 | 以上 |
| ~= | 1 | 正規表現マッチ |
検索・フォールバック
| op | 引数 | 説明 |
|---|---|---|
| coalesce | >=1 | 最初の非null/missing値 |
| lookup | 2-4 | 配列検索(全件) |
| lookup_first | 2-4 | 配列検索(先頭) |
JSON操作
| op | 引数 | 説明 |
|---|---|---|
| merge | >=1 | オブジェクトのシャローマージ |
| deep_merge | >=1 | オブジェクトのディープマージ |
| get | 1 | ドットパスで値を取得 |
| pick | >=1 | 指定パスのみ抽出 |
| omit | >=1 | 指定パスを除外 |
| keys | 0 | オブジェクトのキー配列 |
| values | 0 | オブジェクトの値配列 |
| entries | 0 |
{key, value} の配列 |
| len | 0 | 文字列/配列/オブジェクトの長さ |
| from_entries | >=1 | エントリからオブジェクト構築 |
| object_flatten | 1 | ネストをフラット化 |
| object_unflatten | 1 | フラットをネストに復元 |
配列操作(変換)
| op | 引数 | 説明 |
|---|---|---|
| map | 1 | 各要素を変換 |
| filter | 1 | 条件に一致する要素のみ |
| flat_map | 1 | 変換後フラット化 |
| flatten | 0-1 | 配列のフラット化 |
配列操作(抽出)
| op | 引数 | 説明 |
|---|---|---|
| first | 0 | 先頭要素 |
| last | 0 | 末尾要素 |
| take | 1 | 先頭/末尾からN件取得 |
| drop | 1 | 先頭/末尾からN件除外 |
| slice | 1-2 | 範囲指定で取得 |
| chunk | 1 | N件ずつ分割 |
配列操作(結合)
| op | 引数 | 説明 |
|---|---|---|
| zip | >=1 | 複数配列をペア配列に |
| zip_with | >=2 | 複数配列を結合関数で結合 |
| unzip | 0 | ペア配列を複数配列に分離 |
配列操作(グループ化)
| op | 引数 | 説明 |
|---|---|---|
| group_by | 1 | キーでグループ化(オブジェクト) |
| key_by | 1 | キーでインデックス化(1件のみ) |
| partition | 1 | 条件で2分割 |
配列操作(重複処理)
| op | 引数 | 説明 |
|---|---|---|
| unique | 0 | 重複除去(順序保持) |
| distinct_by | 1 | キー基準で重複除去 |
配列操作(ソート・検索)
| op | 引数 | 説明 |
|---|---|---|
| sort_by | 1 | キー基準で昇順ソート |
| find | 1 | 条件に一致する最初の要素 |
| find_index | 1 | 条件に一致する最初のインデックス |
| index_of | 1 | 値のインデックスを取得 |
| contains | 1 | 値が含まれるか判定 |
配列操作(集計)
| op | 引数 | 説明 |
|---|---|---|
| sum | 0 | 合計 |
| avg | 0 | 平均 |
| min | 0 | 最小値 |
| max | 0 | 最大値 |
| reduce | 1 | 畳み込み(初期値=最初の要素) |
| fold | 2 | 畳み込み(初期値指定) |
型変換
| op | 引数 | 説明 |
|---|---|---|
| string | 0 | 文字列に変換 |
| int | 0 | 整数に変換 |
| float | 0 | 浮動小数点に変換 |
| bool | 0 | 真偽値に変換 |
詳細解説
参照(Reference)
参照は @ 付きの名前空間 + ドットパスで指定します。
5つの名前空間
| 名前空間 | 説明 | 例 |
|---|---|---|
@input.* |
入力レコード | @input.user.name |
@context.* |
実行時注入の外部データ | @context.tenant_id |
@out.* |
先行マッピングの結果 | @out.fullName |
@item / @item.*
|
map/filter内の現在要素 |
@item, @item.name, @item.index
|
@<var> |
letで束縛した変数 |
@total, @base
|
特別な参照
| 参照 | 説明 |
|---|---|
$ |
現在のパイプ値(直前のステップの結果) |
@item |
map/filter内の現在の配列要素 |
@item.index |
現在の要素のインデックス(0始まり) |
source での省略ルール
source では 単一キー の場合のみ名前空間を省略できます。
# OK: 単一キーは省略可能
- target: "id"
source: "id" # @input.id と同等
# ドットパスや配列インデックスは明示的に指定
- target: "name"
source: "input.user.name"
expr では名前空間を明示
expr 内では常に @ 付きで名前空間を明示します。
# OK
expr:
- "@input.id"
# NG(エラー)
expr:
- "id"
リテラル文字列として @ を使用
@ や $ をリテラルとして扱う場合は lit: プレフィックスを使用します。
expr:
- "lit:@example.com" # 文字列 "@example.com"
式(expr)パイプ構文
expr はパイプ配列形式で記述します。左から右へ順に評価され、各ステップの結果が次のステップの入力となります。
基本構文
expr:
- "@input.name" # 開始値
- trim # ステップ1: 前後空白削除
- uppercase # ステップ2: 大文字化
入力: {"name": " alice "}
出力: "ALICE"
引数付きオペレーション
expr:
- "@input.price"
- multiply: [1.1] # price × 1.1
- round: [2] # 小数第2位で丸め
入力: {"price": 100}
出力: 110.0
単一参照の省略形
パイプが参照のみの場合は配列を省略できます。
# 配列形式
expr:
- "@input.name"
# 省略形
expr: "@input.name"
ネストしたオペレーション
引数内で別のパイプを使用できます。
expr:
- "@input.base_price"
- multiply: ["@context.exchange_rate"]
入力: {"base_price": 100}, コンテキスト: {"exchange_rate": 1.5}
出力: 150.0
ステップ
パイプ内で使用できるステップの種類です。
Op ステップ(オペレーション)
# 引数なし
- trim
- uppercase
# 引数あり(短縮形)
- multiply: [2]
- concat: ["@out.suffix"]
# 引数あり(明示形)
- { op: "replace", args: ["old", "new", "all"] }
Let ステップ(変数束縛)
中間結果を変数に保存し、後続のステップで参照できます。
expr:
- "@input.price"
- let: { base: "$" } # $(現在値)を base に束縛
- multiply: [1.1]
- let: { with_tax: "$" } # 税込価格を with_tax に束縛
- if:
cond:
gt: ["@base", 100] # base を参照
then:
- "@with_tax"
- multiply: [0.9] # 割引適用
else:
- "@with_tax"
If ステップ(条件分岐)
expr:
- "@input.price"
- if:
cond:
gt: ["$", 100] # 条件: 100より大きい
then:
- "$"
- multiply: [0.9] # 10%割引
else:
- "$" # そのまま
入力: {"price": 150}
出力: 135.0
Map ステップ(配列変換)
配列の各要素にパイプを適用します。@item で現在の要素を参照します。
expr:
- "@input.items"
- map:
- multiply: [2] # 各要素を2倍
入力: {"items": [1, 2, 3]}
出力: [2.0, 4.0, 6.0]
@item を明示的に使用する例:
expr:
- "@input.users"
- map:
- "@item.name" # 各ユーザーの name を抽出
- uppercase
let と組み合わせた例:
expr:
- "@input.items"
- map:
- let: { doubled: ["$", multiply: [2]] }
- "@doubled"
入力: {"items": [1, 2, 3]}
出力: [2.0, 4.0, 6.0]
条件構文(Conditions)
record_when、when、if.cond で使用する条件構文です。
比較演算子
| 条件 | 説明 |
|---|---|
eq: [a, b] |
等価(厳密一致、型も比較) |
ne: [a, b] |
非等価 |
gt: [a, b] |
a > b |
gte: [a, b] |
a >= b |
lt: [a, b] |
a < b |
lte: [a, b] |
a <= b |
match: [str, regex] |
正規表現マッチ |
注意:
eq/neは JSON の厳密一致です。"1"と1は一致しません。
論理演算子
| 条件 | 説明 |
|---|---|
all: [cond1, cond2, ...] |
AND(すべて true) |
any: [cond1, cond2, ...] |
OR(いずれか true) |
例
# 単純な比較
when:
eq: ["@input.status", "active"]
# 数値比較
when:
gt: ["@input.score", 80]
# 正規表現マッチ
when:
match: ["@input.email", ".*@example\\.com$"]
# 複合条件(AND)
when:
all:
- gt: ["@input.age", 18]
- eq: ["@input.verified", true]
# 複合条件(OR)
when:
any:
- eq: ["@input.tier", "premium"]
- eq: ["@input.tier", "gold"]
ドットパス構文
ドットパスは以下の構文をサポートします。
| 構文 | 説明 | 例 |
|---|---|---|
field.nested |
ネストしたオブジェクト | @input.user.profile |
array[N] |
配列の要素(0始まり) | @input.items[0] |
field["key"] |
ドットを含むキー | @input.user["profile.name"] |
配列インデックス
# 配列の要素にアクセス
expr:
- "@input.items[0].id"
# 多次元配列
expr:
- "@context.matrix[1][0]"
注意: 範囲外インデックスや存在しないパスは
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"
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"
|
大文字小文字無視 |
# mapping の type オプション
- target: "count"
source: "count"
type: "int"
# パイプ内の型変換オペレーション
- target: "id"
expr:
- "@input.id"
- string
record_when レコードフィルタリング
record_when はルートレベルで指定し、マッピング処理の前にレコード単位でフィルタリングします。
version: 2
input:
format: json
json: {}
record_when:
gt: ["@input.score", 10]
mappings:
- target: "id"
source: "id"
# score が 10 より大きいレコードのみ変換される
複合条件
record_when:
all:
- gte: ["@input.age", 18]
- match: ["@input.email", "@company\\.com$"]
# 18歳以上かつ社内メールアドレスのレコードのみ
注意:
record_whenでは@input.*と@context.*のみ参照可能です。@out.*は参照できません。
when 条件分岐
when オプションを使うと、条件に応じてマッピングをスキップできます。
- target: "premiumBadge"
value: true
when:
eq: ["@input.tier", "premium"]
- target: "adultVerified"
value: true
when:
all:
- gte: ["@input.age", 18]
- eq: ["@input.verified", true]
注意:
whenがfalseまたは評価エラーの場合、そのマッピングはスキップされます。
演算子詳細
文字列操作
concat
パイプ値と引数を文字列連結します。
expr:
- "Hello, "
- concat: ["@out.name"]
入力: {"name": "Alice"} (nameは先に処理済み)
出力: "Hello, Alice"
to_string
値を文字列に変換します。
expr:
- "@input.age"
- to_string
入力: {"age": 25}
出力: "25"
trim
前後の空白を削除します。
expr:
- "@input.name"
- trim
入力: {"name": " 田中 "}
出力: "田中"
lowercase
小文字に変換します。
expr:
- "@input.code"
- lowercase
入力: {"code": "ABC"}
出力: "abc"
uppercase
大文字に変換します。
expr:
- "@input.code"
- uppercase
入力: {"code": "abc"}
出力: "ABC"
replace
文字列を置換します。3番目の引数で置換モードを指定できます。
# デフォルト(先頭一致のみ)
expr:
- "@input.text"
- replace: ["abc", "XYZ"]
# "abc-123-abc" -> "XYZ-123-abc"
# all(全置換)
expr:
- "@input.text"
- replace: ["abc", "XYZ", "all"]
# "abc-123-abc" -> "XYZ-123-XYZ"
# regex(正規表現、先頭一致)
expr:
- "@input.text"
- replace: ["\\d+", "NUM", "regex"]
# "abc-123-456" -> "abc-NUM-456"
# regex_all(正規表現、全置換)
expr:
- "@input.text"
- replace: ["\\d", "_", "regex_all"]
# "abc-123" -> "abc-___"
split
区切り文字で分割して配列を返します。
expr:
- "@input.tags"
- split: [","]
入力: {"tags": "a,b,c"}
出力: ["a", "b", "c"]
pad_start
指定長になるまで先頭を埋めます。
expr:
- "@input.code"
- pad_start: [5, "0"]
入力: {"code": "42"}
出力: "00042"
pad_end
指定長になるまで末尾を埋めます。
expr:
- "@input.name"
- pad_end: [10, "_"]
入力: {"name": "ABC"}
出力: "ABC_______"
数値演算
加算
expr:
- "@input.a"
- +: ["@input.b", "@input.c"]
入力: {"a": 1, "b": 2, "c": 3}
出力: 6
減算
expr:
- "@input.total"
- -: [4]
入力: {"total": 10}
出力: 6
乗算
expr:
- "@input.price"
- multiply: [1.1]
入力: {"price": 100}
出力: 110.0
除算
expr:
- "@input.total"
- /: [2]
入力: {"total": 9}
出力: 4.5
round
四捨五入します。引数で小数桁数を指定できます。
# 整数に丸める
expr:
- "@input.value"
- round
# 12.5 -> 13
# 小数第2位まで
expr:
- "@input.value"
- round: [2]
# 12.345 -> 12.35
to_base
整数をN進数の文字列に変換します(2-36進数対応)。
# 16進数変換
expr:
- "@input.color"
- to_base: [16]
# 255 -> "ff"
# 2進数変換
expr:
- 10
- to_base: [2]
# -> "1010"
複合計算例(華氏→摂氏変換)
expr:
- "@input.fahrenheit"
- -: [32]
- multiply: [5]
- /: [9]
- round: [2]
入力: {"fahrenheit": 98.6}
出力: 37.0
日付処理
date_format
日時文字列を別のフォーマットに変換します。
# 基本
expr:
- "@input.date"
- date_format: ["%Y/%m/%d"]
# "2024-01-02" -> "2024/01/02"
# タイムゾーン指定
expr:
- "@input.datetime"
- date_format: ["%Y-%m-%d %H:%M:%S", "UTC", "+09:00"]
対応フォーマット:
- ISO 8601:
2024-01-02T15:04:05Z - RFC 2822/3339
-
YYYY-MM-DD/YYYY/MM/DD
to_unixtime
日時文字列をUnix時間に変換します。
# 秒単位
expr:
- "1970-01-01T00:00:01Z"
- to_unixtime
# -> 1
# ミリ秒単位
expr:
- "@input.timestamp"
- to_unixtime: ["ms"]
論理演算
and
expr:
- "@input.verified"
- and: ["@input.active"]
or
expr:
- "@input.is_admin"
- or: ["@input.is_owner"]
not
expr:
- "@input.disabled"
- not
入力: {"disabled": false}
出力: true
比較演算
注意: 条件構文(
when,record_when,if.cond)ではeq,ne,gtなどの専用形式を使用してください。
等価比較
expr:
- "@input.status"
- ==: ["active"]
数値比較
expr:
- "@input.score"
- >=: [60]
# 75 -> true
正規表現マッチ
expr:
- "@input.email"
- ~=: [".+@example\\.com$"]
# "test@example.com" -> true
検索・フォールバック
coalesce
最初の「missing でも null でもない」値を返します。
expr:
- "@input.nickname"
- coalesce: ["@input.name", "unknown"]
lookup
配列を検索し、マッチした要素を 配列 で返します。
# from を明示
expr:
- lookup:
- "@context.tags"
- "id"
- "@input.tag_id"
- "value"
# パイプ値を配列として使用
expr:
- "@context.tags"
- lookup:
- "id"
- "@input.tag_id"
- "value"
lookup_first
配列を検索し、最初にマッチした要素を返します。
expr:
- lookup_first:
- "@context.departments"
- id
- "@input.dept_id"
- name
入力: {"dept_id": 1}, コンテキスト: {"departments": [{"id": 1, "name": "営業"}]}
出力: "営業"
JSON操作
merge
複数のオブジェクトをシャローマージします(後勝ち)。
expr:
- "@input.base"
- merge: ["@input.override"]
# {"a": 1, "b": 2} + {"b": 3, "c": 4} -> {"a": 1, "b": 3, "c": 4}
deep_merge
ネストしたオブジェクトも再帰的にマージします。
expr:
- "@input.base"
- deep_merge: ["@input.override"]
# {"a": {"x": 1}} + {"a": {"y": 2}} -> {"a": {"x": 1, "y": 2}}
get
ドットパスで値を取得します。
expr:
- "@input.obj"
- get: ["user.profile.name"]
pick
指定パスのみを抽出した新しいオブジェクトを返します。
expr:
- "@input.obj"
- pick: ["id", "profile.name"]
omit
指定パスを除外した新しいオブジェクトを返します。
expr:
- "@input.obj"
- omit: ["password", "internal.secret"]
keys
オブジェクトのキーを配列で返します。
expr:
- "@input.map"
- keys
# {"a": 1, "b": 2} -> ["a", "b"]
values
オブジェクトの値を配列で返します。
expr:
- "@input.map"
- values
# {"a": 1, "b": 2} -> [1, 2]
entries
オブジェクトを {key, value} の配列に変換します。
expr:
- "@input.map"
- entries
# {"a": 1, "b": 2} -> [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
len
文字列/配列/オブジェクトの長さを返します。
# 配列の長さ
expr:
- "@input.array"
- len
# [1, 2, 3] -> 3
# 文字列の長さ(Unicode文字単位)
expr:
- "@input.text"
- len
# "こんにちは" -> 5
from_entries
エントリからオブジェクトを構築します。
# [key, value]形式の配列
expr:
- "@input.pairs"
- from_entries
# [["a", 1], ["b", 2]] -> {"a": 1, "b": 2}
# 2引数: キーと値を直接指定
expr:
- from_entries: ["status", "@input.code"]
# {"code": 200} -> {"status": 200}
object_flatten
ネストしたオブジェクトをドットパスキーでフラット化します。
expr:
- "@input.obj"
- object_flatten
# {"user": {"profile": {"age": 30}}} -> {"user.profile.age": 30}
object_unflatten
ドットパスキーのオブジェクトをネスト構造に復元します。
expr:
- "@input.flat"
- object_unflatten
# {"user.name": "田中"} -> {"user": {"name": "田中"}}
配列操作(変換)
map
各要素に対してパイプを適用し、結果の配列を返します。
expr:
- "@input.numbers"
- map:
- multiply: [2]
# [1, 2, 3] -> [2.0, 4.0, 6.0]
filter
条件に一致する要素のみを含む配列を返します。
expr:
- "@input.numbers"
- filter:
- >: [2]
# [1, 2, 3, 4] -> [3, 4]
flat_map
各要素を変換後、結果をフラット化します。
expr:
- "@input.nested"
- flat_map:
- "@item"
# [[1, 2], [3, 4]] -> [1, 2, 3, 4]
flatten
配列を指定の深さまでフラット化します。
# 1階層(デフォルト)
expr:
- "@input.nested"
- flatten
# [[1, 2], [3, 4]] -> [1, 2, 3, 4]
# 2階層
expr:
- "@input.nested"
- flatten: [2]
配列操作(抽出)
first
配列の先頭要素を返します。
expr:
- "@input.items"
- first
# [1, 2, 3] -> 1
last
配列の末尾要素を返します。
expr:
- "@input.items"
- last
# [1, 2, 3] -> 3
take
先頭または末尾からN件取得します。
# 先頭2件
expr:
- "@input.items"
- take: [2]
# [1, 2, 3, 4, 5] -> [1, 2]
# 末尾2件(負数)
expr:
- "@input.items"
- take: [-2]
# [1, 2, 3, 4, 5] -> [4, 5]
drop
先頭または末尾からN件除外します。
# 先頭2件を除外
expr:
- "@input.items"
- drop: [2]
# [1, 2, 3, 4, 5] -> [3, 4, 5]
slice
範囲指定で要素を取得します(end は排他)。
expr:
- "@input.items"
- slice: [1, -1]
# [1, 2, 3, 4, 5] -> [2, 3, 4](インデックス1から末尾-1まで)
chunk
配列をN件ずつの配列に分割します。
expr:
- "@input.items"
- chunk: [2]
# [1, 2, 3, 4, 5] -> [[1, 2], [3, 4], [5]]
配列操作(結合)
zip
複数の配列をペアの配列に結合します。
expr:
- "@input.keys"
- zip: ["@input.values"]
# keys: ["a", "b"], values: [1, 2] -> [["a", 1], ["b", 2]]
zip_with
複数の配列を結合関数で処理します。
expr:
- "@input.nums"
- zip_with:
- "@input.strs"
- concat: ["@item[1]"]
# nums: [1, 2], strs: ["a", "b"] -> ["1a", "2b"]
unzip
ペア配列を複数の配列に分離します。
expr:
- "@input.pairs"
- unzip
# [["a", 1], ["b", 2]] -> [["a", "b"], [1, 2]]
配列操作(グループ化)
group_by
キー式でグループ化し、オブジェクトを返します。
expr:
- "@input.items"
- group_by:
- "@item.category"
key_by
キー式でインデックス化し、オブジェクトを返します(重複時は後勝ち)。
expr:
- "@input.items"
- key_by:
- "@item.id"
partition
条件で配列を2分割します。[true配列, false配列] を返します。
expr:
- "@input.numbers"
- partition:
- >: [2]
# [1, 2, 3, 4] -> [[3, 4], [1, 2]]
配列操作(重複処理)
unique
配列から重複を除去します(順序保持)。
expr:
- "@input.tags"
- unique
# ["a", "b", "a", "c", "b"] -> ["a", "b", "c"]
distinct_by
キー式基準で重複を除去します(最初の要素を保持)。
expr:
- "@input.items"
- distinct_by:
- "@item.id"
配列操作(ソート・検索)
sort_by
キー式基準で昇順ソートします。
expr:
- "@input.items"
- sort_by:
- "@item.score"
find
条件に一致する最初の要素を返します(見つからなければ null)。
expr:
- "@input.items"
- find:
- >: [2]
# [1, 2, 3, 4] -> 3
find_index
条件に一致する最初の要素のインデックスを返します(見つからなければ -1)。
expr:
- "@input.items"
- find_index:
- >: [2]
# [1, 2, 3, 4] -> 2
index_of
値のインデックスを返します(見つからなければ -1)。
expr:
- "@input.items"
- index_of: [3]
# [1, 2, 3, 4] -> 2
contains
配列に値が含まれるかを判定します。
expr:
- "@input.tags"
- contains: ["premium"]
# ["basic", "premium"] -> true
配列操作(集計)
sum
数値配列の合計を返します。
expr:
- "@input.numbers"
- sum
# [1, 2, 3, 4] -> 10
avg
数値配列の平均を返します。
expr:
- "@input.numbers"
- avg
# [1, 2, 3, 4] -> 2.5
min
数値配列の最小値を返します。
expr:
- "@input.numbers"
- min
# [3, 1, 4, 1, 5] -> 1
max
数値配列の最大値を返します。
expr:
- "@input.numbers"
- max
# [3, 1, 4, 1, 5] -> 5
reduce
配列を畳み込みます。初期値は最初の要素です。
expr:
- "@input.numbers"
- reduce:
- +: ["@item"]
# [1, 2, 3, 4] -> 10
fold
初期値を指定して配列を畳み込みます。
expr:
- "@input.numbers"
- fold:
- 100
- +: ["@item"]
# [1, 2, 3, 4] -> 110
型変換
string
値を文字列に変換します。
expr:
- "@input.id"
- string
int
値を整数に変換します。
expr:
- "@input.count"
- int
float
値を浮動小数点に変換します。
expr:
- "@input.value"
- float
bool
値を真偽値に変換します。
expr:
- "@input.flag"
- bool
DTO自動生成
rulemorph はルールファイルから7言語のDTO定義を自動生成できます。
対応言語
- Rust
- TypeScript
- Python
- Go
- Java
- Kotlin
- Swift
TypeScript 出力例
ルールファイル:
version: 2
input:
format: json
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のフィールドは必須プロパティに、それ以外はオプショナル(?)になります。
完成例
基本: 文字列加工とパイプ
version: 2
input:
format: json
json: {}
mappings:
- target: name
expr:
- "@input.name"
- trim
- uppercase
- target: greeting
expr:
- "Hello, "
- concat: ["@out.name"]
- target: price
expr:
- "@input.base_price"
- multiply: ["@context.exchange_rate"]
- target: currency
value: "USD"
入力: [{"name": " alice ", "base_price": 100}]
コンテキスト: {"exchange_rate": 1.5}
出力:
[{
"name": "ALICE",
"greeting": "Hello, ALICE",
"price": 150.0,
"currency": "USD"
}]
let/if/map ステップ
version: 2
input:
format: json
json: {}
mappings:
- target: total_with_tax
expr:
- "@input.price"
- let: { base: "$" }
- multiply: [1.1]
- target: discount_applied
expr:
- "@input.price"
- if:
cond:
gt: ["$", 100]
then: ["$", multiply: [0.9]]
else: ["$"]
- target: doubled_items
expr:
- "@input.items"
- map:
- multiply: [2]
- target: tier
expr:
- "@input.price"
- let: { p: "$" }
- if:
cond:
gte: ["@p", 200]
then: ["gold"]
else:
- if:
cond:
gte: ["@p", 100]
then: ["silver"]
else: ["bronze"]
入力: [{"price": 150, "items": [10, 20]}]
出力:
[{
"total_with_tax": 165.0,
"discount_applied": 135.0,
"doubled_items": [20.0, 40.0],
"tier": "silver"
}]
条件構文
version: 2
input:
format: json
json: {}
mappings:
- target: is_premium_active
expr:
- "@input.tier"
- if:
cond:
all:
- eq: ["$", "premium"]
- eq: ["@input.status", "active"]
then: [true]
else: [false]
- target: is_email
expr:
- "@input.contact"
- if:
cond:
match: ["$", ".*@.*"]
then: [true]
else: [false]
lookup によるマスターデータ結合
version: 2
input:
format: json
json: {}
mappings:
- target: dept_name
expr:
- lookup_first:
- "@context.departments"
- id
- "@input.dept_id"
- name
- target: all_projects
expr:
- lookup:
- "@context.projects"
- owner_id
- "@input.id"
- name
入力: [{"id": 1, "dept_id": 10}]
コンテキスト:
{
"departments": [{"id": 10, "name": "営業"}],
"projects": [
{"owner_id": 1, "name": "Project A"},
{"owner_id": 1, "name": "Project B"}
]
}
出力:
[{
"dept_name": "営業",
"all_projects": ["Project A", "Project B"]
}]
map 内での let 使用
version: 2
input:
format: json
json: {}
mappings:
- target: results
expr:
- "@input.items"
- map:
- let: { doubled: ["$", multiply: [2]] }
- "@doubled"
入力: [{"items": [1, 2, 3]}]
出力: [{"results": [2.0, 4.0, 6.0]}]
トラブルシューティング
よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
| missing required field |
required=true で値が missing または null
|
default を追加、または required=false に変更 |
| type cast failed | 型変換不可(例: "abc" を int に変換) |
入力値を確認、型指定を見直し |
| forward out reference | 未定義の @out.* を参照 |
マッピングの順序を確認(上から順に評価) |
| invalid reference | 不正な参照形式 |
@ 付きの名前空間を確認 |
| unknown operation | 未知のオペレーション名 | オペレーション名のスペルを確認 |
| division by zero | 除算の分母が 0 | 入力値を確認 |
デバッグのヒント
- 段階的に構築: 複雑なパイプは一度に書かず、シンプルなステップから始めて徐々に追加
-
missing vs null: 意図しない
nullはcoalesceでフォールバック -
型変換エラー: 入力データの型を確認、必要なら
stringで文字列化してから処理 - out 参照: マッピングの順序を確認、参照先が先に定義されているか確認
-
条件構文:
when/if.condではeq,gtなどの専用形式を使用
関連リンク
- GitHub: https://github.com/vinhphatfsg/rulemorph
- 公式仕様書:
docs/rules_spec_ja.md,docs/rules_spec_en.md