値式 (Value Expressions)

Script で値が必要となるすべての箇所(URL、リクエスト body、フィールド値、条件、フィルター値、対象 id など)は、以下の 3 つの形式のいずれかです。例外は 2 つだけです。RegexpatternCachekeyはリテラルとしてのみ書き、その中の { /pointer } は値に置き換えられません。このドキュメントでは、その 3 つの形式と、値がどこから来るのか(コンテキストルート)、そして WEEGLOO データ特有のロケールマップのルールを説明します。Statement カタログのすべてのフィールドがこのルールに従います。

3 つの形式

形式ルール
参照 (reference)文字列内の { /json-pointer } をコンテキストに対して resolve します。"{ /payload/fields/title }"
リテラル (literal){ /ptr } を含まない値(文字列、数値、ブーリアン、オブジェクト、配列)です。そのまま使用します。"draft", 42, true, { "a": 1 }
演算と条件 (JsonLogic)単一のキーが演算子であるオブジェクトです。そのオペランドは、それ自体が値式(参照、リテラル、ネストした演算)です。位置によっては演算子に $ が必要です。{ "$+": [ "{ /vars/n }", 1 ] }

3 つの形式はネストできます。JsonLogic のオペランドに参照を、参照の結果を再び演算に入れる形で組み合わせます。

データ位置と式位置: $ をいつ付けるか

同じ JSON でも、位置によって読まれ方が変わります。分かれ目はその位置のキーを誰が所有しているかです。fields のキーは Content Type のフィールド id であり、Http.body のキーは呼び出し先 API のスキーマなので、そうした位置での catin は演算子ではなくフィールド名でなければなりません。

位置該当するフィールド読まれ方
データ位置fields(ResourceCreate, ResourceUpdate, ResourcePatch), Http.body, Return.value, SetVar.value, Cache.value, Cache.defaultValue$ が付いていないキーは常にフィールド名です。演算を使うには $ を付けます。
式位置If.condition, Loop.while, version値全体が式です。演算子は cat でも $cat でも構いません。
テンプレート位置それ以外すべて(url, method, headers[].value, locale, order, over, target.sys.id, EmailSend のフィールド、SignatureHashRegex の値フィールド)文字列なので { /pointer } だけが入ります。
リテラル専用Regex.pattern, Cache.key値式ではありません。Regex.pattern に書いた { /pointer } は置換されず、パターンの一部になります。

ルールは 2 行です。

  1. データ位置で $ が付いていないキーは、常にフィールド名です。演算を使うには演算子に $ を付けます。
  2. いったん $ で式に入ったら、その内側はすべて式です。ネストした演算子に $ は不要です(付けても構いません)。

迷ったら、すべての演算子に $ を付けてください。どの位置でも正しくなります。

// データ位置: cat は Content Type のフィールド名です (連結の演算ではありません)
"fields": { "cat": { "en-US": "hello" } }
 
// データ位置での計算: 境界にだけ $、その内側はそのまま
"fields": { "tier": { "en-US": { "$if": [ { ">=": [ "{ /p/score }", 700 ] }, "gold", "silver" ] } } }
 
// 式位置: そのまま書きます
"condition": { "and": [ { "<": [ "{ /a/body/risk }", 0.5 ] }, { ">=": [ "{ /b/body/score }", 700 ] } ] }

$ で始まるフィールド名が必要なとき: $$

JSON Schema の $ref$schema のように、キーが実際に $ で始まらなければならない場合は、$ を 2 回書きます。"$$ref" はデータキー $ref を意味します。先頭の $1 つだけ取り除かれ($$$ref$$ref になります)、キーにのみ適用されます(値の中の $ はそのままです)。

"body": { "$$ref": "#/components/schemas/Item", "topK": { "$min": [ "{ /payload/fields/k }", 50 ] } }

拒否される 2 つのケース

以下の 2 つの場合は、黙って別の意味に解釈されるのではなく、エラーとして拒否されます。

  • $ キーが同じオブジェクトの他のキーと同居している場合はエラーです。演算はそのオブジェクトの唯一のキーでなければならず、兄弟のデータは 1 段外に出せば済みます。
  • 未知の $ キーはエラーです。$catt$catt という名前のフィールドではありません。$ の名前空間は演算子用に予約されています。

式位置では、演算子名が兄弟キーと同居しているのもエラーです({ "and": […], "or": […] })。その位置にはデータという解釈がなく、すべてのオブジェクトは真と判定されるため、そのままにすると条件が黙って常に真になります。

参照: { /json-pointer }

波括弧の中に RFC 6901 JSON Pointer(必ず / で始まる)を入れます。波括弧の周囲の空白は許可されます({ /a/b }{/a/b} と同じです)。

単一ポインターと混合テンプレート: 型のルール

  • 文字列全体が単一ポインターの場合、その値の元の型をそのまま維持します(数値なら数値、オブジェクトならオブジェクト、配列なら配列)。
  • リテラルテキストと混ざると文字列として連結(concatenation) されます。
"{ /payload/fields/count }"                 // 数値なら数値のまま (例: 42)
"{ /payload/fields/tags }"                  // 配列なら配列のまま
"page-{ /payload/fields/n }-of-10"          // 文字列連結 → "page-42-of-10"
"Bearer { /payload/fields/token }"          // 文字列連結 → "Bearer abc123"

存在しない値

  • パスが存在しないか値が空の場合、単一ポインターは null混合テンプレートは空文字列として処理されます。

コンテキストルート: 値はどこから来るのか

{ /pointer } の最上位セグメントは、以下の 7 つのいずれかです。

ルート内容
/payload呼び出し時に渡された JSON payload(入力)です。例: { /payload/fields/email }
/rawPayload同じ入力を、呼び出し元が送った本文の文字列そのままで保持します(パース前)。例: { /rawPayload }
/headers呼び出し時に渡されたリクエスト HTTP ヘッダーです。キーは小文字で、名前ごとに単一の値です。例: { /headers/authorization }
/now実行が始まった時刻です。{ /now/seconds }{ /now/millis }{ /now/iso }
/<name>その name を持つ、先行する statement の結果です。例: { /order/sys/id }
/vars/<name>SetVar で宣言した script-scoped な可変変数です。例: { /vars/total }
/errorTrycatch ブロック内でのみ使用します。捕捉したエラー { message } です。例: { /error/message }

/<name> を除いた 6 つの名前(payloadrawPayloadheadersnowvarserror)は予約されているため、statement の name には使えません。同じ名前を使うとそのルートを上書きしてしまうので、保存の時点で拒否されます(共通フィールドのバインディング名の規則)。

/rawPayload: 送られたままの本文

/payload はパースされた値で、/rawPayload同じ本文の原文の文字列です。2 つは同じものを指していますが、同じではありません。パースされた値を再び文字列に戻すと、空白、数値表記、エスケープ、重複キーがすべて正規化され、送られたバイトには戻りません。

そのため、送られたバイトの上で計算される値は /rawPayload でしか扱えません。 代表的なのが決済代行会社の Webhook の署名検証です(Signature)。値を取り出して使う普段の参照は /payload で行います。

呼び出しの本文は JSON オブジェクトのみを受け取ります。本文が空なら無いものとして扱い、JSON オブジェクトでなければ(壊れた JSON、配列、スカラー、リテラルの null)実行せずに拒否します(エラー 参照)。

/now: 実行が始まった時刻

/now は、この実行が始まった時刻を 3 つの形で保持します。

ポインター
{ /now/seconds }epoch 秒(整数)
{ /now/millis }epoch ミリ秒(整数)
{ /now/iso }sys.createdAt と同じ、プラットフォームの時刻表記の文字列(UTC)
  • 1 回の実行に時刻は 1 つだけです。 時計を読む statement ではなく、実行が始まるときに埋め込んでおく値なので、2 つの文が互いに違う値を見ることはありません。Parallel の各ブランチも同じ時刻を受け継ぎます。文ではないので statement の数にも数えません。
  • タイムゾーンを選ぶフィールドはありません。 epoch の値はどこでも同じ数であり、iso は UTC 表記です。
  • Webhook の replay window(署名に載ったタイムスタンプが今から何秒以内か)の検証に使います。タイムスタンプは通常文字列で入ってきますが、算術演算が数値に変換してくれるのでそのまま比較します。
// 署名に載ったタイムスタンプが 5 分(300 秒)以内か
{ "<": [ { "-": [ "{ /now/seconds }", "{ /sig/1 }" ] }, 300 ] }

statement の結果の形

name を持つ statement の結果の形は、型ごとに異なります。

statement結果の形参照例
Http{ status, body }{ /resp/status }, { /resp/body/choices/0/message/content }
ResourceCreate, ResourceRead(単一), ResourceFind(単一)リソースそのもの{ /post/sys/id }, { /post/fields/title/en-US }
ResourceForEach(巡回中)name現在の項目 = リソースそのもの。onEach 内でのみ参照{ /post/sys/id }, { /post/fields/title/en-US }
ResourceCountマッチした件数(整数){ /commentCount }
ParseJsonパースされた値そのもの(オブジェクト・配列・スカラー){ /quote/items/0/price }
SignatureBoolean(検証を通ったかどうか){ /verified }
Hash文字列(宣言した表記のダイジェスト){ /expectedSign }
RegexMatchBooleanCapture は配列(0=マッチ全体、1 からがキャプチャグループ)、マッチがなければ null{ /isOrderId }, { /sig/1 }
  • ResourceFind はマッチがない場合 null をバインドします。{ "==": [ "{ /found }", null ] } で存在の有無を分岐します。
  • ResourceRead(単一)は対象が存在しない場合はエラーです(Try で処理できます)。詳しくは Statement カタログのリソース読み取りで扱います。
  • ServiceUser を読み取ると、結果は会員リソースそのものです({ /member/sys/id })。ContentMedia と違い、フィールドがロケールマップではなく値そのままです。規則は 会員ディレクトリの読み取りで扱います。

演算と条件: JsonLogic

計算や条件が必要なときは、jsonlogic.com 仕様の演算子オブジェクトを使います。

  • データアクセスは vanilla の var(dot-path) ではなく { /ptr } 参照に統一します。エンジンはまずオペランドのポインターを resolve し、その後で演算子を適用します。
  • 演算子はそのオブジェクトの唯一のキーでなければなりません。データ位置では $ を付けたキーだけが演算で、式位置では $ の有無に関係なく演算です(データ位置と式位置 を参照)。

演算子の表

表に載っている名前は演算子トークンです。データ位置で使うときは先頭に $ を付けます(cat なら $cat)。式位置ではどちらでも構いません。

分類演算子意味と例
条件if (別名 ?:){ "if": [条件, 真の値, 条件2, 真の値2, …, 既定値] }。最初に真になった条件の値、なければ最後の既定値。
論理and, or短絡評価。and は最初の falsy(または最後)、or は最初の truthy(または最後)を値として返します。
論理! (not), !! (to-bool){ "!": x } は truthy の否定、{ "!!": x } は truthy かどうかです。存在チェックには !! をよく使います。
等価==, !=緩い比較(数値へ強制変換してから比較。"1"==1 は真)。
等価===, !==厳密な比較(型まで含む)。
比較<, <=, >, >=連鎖可能: { "<": [1,2,3] }1<2 AND 2<3 です。数値化できない(NaN)場合は false です。
算術+すべてのオペランドの合計。
算術-オペランドが 1 個なら符号反転、2 個なら減算。
算術*, /, %乗算、除算、剰余。
集計min, maxオペランドの最小値と最大値。
文字列catすべてのオペランドを文字列として連結。
包含in{ "in": [needle, haystack] }。haystack が文字列なら部分文字列、コレクションなら要素の包含。
配列merge複数の配列や値を 1 つの配列に平坦化(累積収集に使う)。
日付date{ "date": [値, 出力単位] }。値を比較できる瞬間に正規化。出力単位は millis(既定)、secondsisoday日付の正規化 を参照。

配列を反復する演算子(map, filter, reduce, all, some, none)はサポートしていません。Script は配列を Loop で反復します(Statement カタログの Loop)。一覧から日付の条件に合う項目だけを絞り込むのも、反復ではなく読み取りの文が行う仕事です。ResourceFindResourceForEachwhere に条件を与えると、サーバーが絞り込んで返します(使える演算子は 演算子一覧 にあります)。

数値変換と例

数値変換のルールは次のとおりです。数値はそのままにし、true は 1、false は 0、文字列はパースし(パースできない場合は計算が失敗します)、null は 0 に変換します。

日付の文字列は数値ではありません。 "2026-10-03" は数値としてパースされないため、比較演算子はエラーを出さずに常に false を返します。日付を比べるには、まず date で正規化します。

以下のスニペットは式位置を前提としています。データ位置(fields, Http.body, Return.value, SetVar.value)に入れるときは、最上位の演算子に $ を付け、内側のオペランドはそのままにします。

{ "-":  [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] }   // 残高 - 費用
{ "<":  [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] }   // 残高 < 費用 → boolean
{ "and": [ { "<": [ "{ /a/body/risk }", 0.5 ] }, { ">=": [ "{ /b/body/score }", 700 ] } ] }
{ "cat": [ "id-", "{ /payload/sys/id }" ] }                                    // "id-<uuid>"
{ "!!": "{ /found/sys/id }" }                                                  // 存在すれば true
{ "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] }                       // 配列の蓄積: SetVar.value はデータ位置なので $
{ "if": [ "{ /payload/fields/next }", "{ /payload/fields/next }", "END" ] }     // next があれば next、なければ "END"

日付の正規化 (date)

比較演算子は、オペランドを数値に変換してから比べます。日付の文字列は数値ではないので、その比較はエラーを出さずに常に false になります。== に変えても解決しません。両方が数値でない場合はテキストをそのまま比べるため、同じ瞬間を違う書き方で表した "2026-10-03""2026-10-03T00:00:00.000Z" が互いに異なる値になります。日付は比べる前に date で正規化します。

{ "date": [ , 出力単位 ] }      // 出力単位は省略できます
{ "date": "2026-10-03" }          // 値を 1 つだけ渡すときは配列を外しても構いません

beforeafterequal 専用の演算子はありません。正規化した値は数値なので、すでにある比較・算術・集計の演算子をそのまま使えば済みます。

判定したいこと使う式
a が b より前{ "<": [ { "date": a }, { "date": b } ] }
a が b より後{ ">": [ { "date": a }, { "date": b } ] }
同じ瞬間{ "==": [ { "date": a }, { "date": b } ] }
同じ日(時刻は無視){ "==": [ { "date": [a, "day"] }, { "date": [b, "day"] } ] }
from と to の間{ "<=": [ { "date": from }, { "date": x }, { "date": to } ] }(連鎖比較)
1 週間後{ "date": [ { "+": [ { "date": x }, 604800000 ] }, "iso" ] }
2 つの日付の日数の差{ "/": [ { "-": [ { "date": a }, { "date": b } ] }, 86400000 ] }
複数の日付のうち最も早いもの{ "min": [ { "date": a }, { "date": b } ] }

算術の結果はまたミリ秒の数値なので、date にもう一度入れて isoday で出力できます(上の表の「1 週間後」)。

// 式位置: クーポンが有効期間内かどうか。3 つの値の表記が互いに違っていても構いません
{ "<=": [
  { "date": "{ /coupon/fields/startsAt/en-US }" },
  { "date": "{ /now/iso }" },
  { "date": "{ /coupon/fields/endsAt/en-US }" }
] }
 
// 式位置: HTTP の Date ヘッダーが今から 5 分(300 秒)以内か
{ "<": [ { "-": [ "{ /now/seconds }", { "date": [ "{ /headers/date }", "seconds" ] } ] }, 300 ] }

読み取れる入力

以下の値はすべて同じ瞬間として読み取られます。

形式
ISO-8601, RFC 33392026-10-03T00:00:00Z, 2026-10-03T00:00:00.000Z, 2026-10-03T09:00:00+09:00
秒や小数点以下を省略した時刻2026-10-03T00:00
T の位置に空白を置いた時刻2026-10-03 00:00:00
日付のみ(UTC の午前 0 時として読み取ります)2026-10-03
RFC 1123(HTTP の Date ヘッダーの表記)Sat, 03 Oct 2026 00:00:00 GMT
epoch の数値と数値文字列1790985600, 1790985600000, "1790985600"
  • offset がなければ UTC として読み取ります。 offset は +09:00+0900+09Z のすべてを受け取ります。
  • パースは厳密です。 桁数が合っていても、実際には存在しない日付(2026-13-45)であれば失敗します。
  • epoch は絶対値の大きさで単位を判別します。 100,000,000,000 未満なら秒、それ以上ならミリ秒です。そのため { /now/seconds }{ /now/millis } のどちらを入れても、それぞれ正しく読み取られます。
  • epoch として認める範囲は、絶対値が 100,000,000 以上 100,000,000,000,000 未満です。 単位を大きさで判別しなければならないので、範囲を両側で区切ってあります。それ以外の数値は 1970 年として読み取られる代わりに失敗します。区切り文字のない日付 20261003、年の 2026、値がないという意味で渡ってくる 0 がこれに該当します。

出力単位

2 番目のオペランドが出力の形を決めます。単位の名前は大文字と小文字を区別しません。

結果使う場面
省略、millisepoch ミリ秒(数値)比較と算術
secondsepoch 秒(数値)。秒未満は切り捨てますepoch 秒を受け取る外部 API
iso2026-10-03T00:00:00.000ZContentDate フィールドへの書き込み
day2026-10-03(UTC 基準)同じ日の比較、画面表記

一覧にない名前を与えると失敗し、エラーメッセージが使える名前を列挙します。

iso 出力と Date フィールドへの書き込み

ContentDate フィールドは、書き込むときに yyyy-MM-ddTHH:mm:ss[.小数点]Z という 1 つの形式だけを受け取ります。T と秒と末尾の Z がすべてそろっている必要があり、小数点以下は入れても外しても構わず、値は UTC として読み取られます。そのため、payload で受け取った 2026-10-032026-10-03T09:00:00+09:00 をそのまま入れると、値が有効でないとして拒否されます。dateiso 出力がちょうどこの形式なので、受け取った日付をフィールドに書き込む前に date を一度通します。

// データ位置: payload の "2026-10-03" をクーポンの有効期限に書き込みます
"fields": { "endsAt": { "en-US": { "$date": [ "{ /payload/fields/endsAt }", "iso" ] } } }

読み取れない値

以下の 3 つの場合、その文は失敗します(status 400)。実行中に起きる失敗なので、Trycatch で局所的に処理できます。

  • 最初のオペランドがない、または参照が値を見つけられませんでした。
  • 値を日付として読み取れません。空文字列、空白だけの文字列、日付ではない文字列、存在しない日付、ブーリアン、オブジェクト、認める範囲の外の数値がこれに該当します。
  • 出力単位の名前が一覧にありません。

値がないときに null を返さないのは、意図した契約です。null は数値変換で 0 になり 1970 年として比べられるため、日付が欠けた検査は失敗するのではなく結果が反転します。有効期間を過ぎたクーポンが通ってしまうほうが、実行が止まるよりも悪いのです。

真偽の判定 (Truthiness)

ifandor!!!If.conditionLoop.while は、以下のルールで真偽を判定します。

  • falsy: nullfalse、数値 0、空文字列 ""、空のコレクション(空の配列)。
  • truthy: それ以外すべて(0 でない数値、空でない文字列と配列、すべてのオブジェクト)。

キーも参照できます

fields のようなマップのキー{ /ptr } 参照をサポートします。キーは実行時に resolve されます。

"fields": { "{ /payload/fields/fieldName }": { "en-US": "{ /payload/fields/fieldValue }" } }

2 つのキーが同じ値に resolve されると、衝突してエンジンエラーになります。

ロケールマップ: Content と Media 特有のルール

WEEGLOO の ContentMedia の各フィールドは、値ではなくロケールごとのマップです(例: balance{ "en-US": 1, "ko-KR": 10 })。そのため、読み書きの際はロケールも一緒に扱う必要があります。Mediatitledescription(スカラー)、file(インジェスト指示)がロケールマップです。/payload や HTTP レスポンスのように Content でも Media でもない JSON は、このルールとは無関係です(スキーマが定めた構造そのままで、スカラーならスカラーです)。

読み取り

  • スカラーを得るには ロケールまで指定します: { /<name>/fields/<field>/<locale> }(例: { /post/fields/title/en-US })。
  • ロケールを指定せず { /<name>/fields/<field> } とすると、ロケールマップのオブジェクト全体が返ります。
  • localized:false のフィールドは既定ロケールのバケットにのみ存在するため、その既定ロケールのコードで読み取ります。

書き込み (ResourceCreate, ResourceUpdate, ResourcePatch の fields)

値はロケールマップ { "<locale>": <スカラー値式> } です。読み取りと対称です。

"fields": {
  "title":  { "en-US": "Hello", "ko-KR": "안녕" },   // 複数のロケールはバケットを列挙
  "status": { "en-US": "paid" }
}
  • ResourceCreate は populate するすべてのフィールドに Space の既定ロケールのバケットを必ず含める必要があります(default-locale ルール)。
  • ResourceUpdate は全体の置き換えです。fields にないフィールドとロケールは削除されます(file を含む)。
  • ResourcePatch は指定したフィールドとバケットのみを更新します(残りのフィールドとロケールは維持)。
  • リテラルの null で削除します: 値がリテラルの null であれば、その (field, locale) バケットを削除します(Patch で特定のロケールを空にする際の標準)。""(空文字列)は削除ではなく空の値の設定です。値式({ /ptr })が実行時に null と評価される場合は、削除ではなくエラーです(payload の欠落を黙って飲み込むことはありません)。削除はリテラルの null のみが該当します。
  • Mediafile: 値はスカラーではなくインジェスト指示 { "source": …, "encoding": "url"|"base64" } です。インジェストが実際に何をするのかは Statement カタログの ResourceCreateで扱います。
  • localized:false のフィールドは既定ロケールのバケットにのみ入れます。
  • ロケールコード(マップのキー)も { /ptr } 参照が可能です(上記 キーも参照できます を参照)。動的なロケールを作るときに使います。

locale 便宜フィールド

ResourceCreateResourceUpdateResourcePatchlocale を与えると、エンジンが fields各値を自動的に { <locale>: 値 } バケットで包みます。 つまり、スカラーだけを与えればよいのです。

// 以下の2つは同じ
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
  "locale": "en-US", "fields": { "title": "Hello" } }
 
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
  "fields": { "title": { "en-US": "Hello" } } }

locale を与えつつ、値にすでにロケールマップ({ "en-US": … })をネストしていると、{ <locale>: { "en-US": … } } のように二重にネストされます(作成者のミス)。locale を使うならスカラーのみ、使わないなら明示的なロケールマップのみ、というように 1 つに統一します。

where と order のロケール

  • whereorder では、fields.X にエンジンが Space の既定ロケールを自動的に適用します(CMA の照会と同じ)。
  • 特定のロケールを指定するには、fields.X.<locale> のように明示します。
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }   // 既定ロケールの slug
"where": { "fields.title.ko-KR": { "prefix": "안" } }              // 特定のロケール

エラー

値式の規則に違反したときに出るコードです。保存時に検査され、定義のほかの静的制約に反したコードは 実行セマンティクス、制約、セキュリティのエラー に、呼び出し時に出るコードは エンドポイントのエラー にあります。すべてのリソースに共通するコードは 共通エラー を参照してください。

コード条件
WGL400056データ位置で、$ の演算キーを同じオブジェクトの他のキーと同居させました。
WGL400055データ位置に、演算子として定義されていない $ キーを書きました。