0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

rulemorph リファレンス

0
Last updated at Posted at 2026-01-14

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 入力値を確認

デバッグのヒント

  1. 段階的に構築: 複雑な式は一度に書かず、シンプルなマッピングから始めて徐々に追加
  2. missing vs null: 意図しない null は coalesce でフォールバック
  3. 型変換エラー: 入力データの型を確認、必要なら to_string で文字列化してから処理
  4. out 参照: マッピングの順序を確認、参照先が先に定義されているか確認

関連リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?