值表达式 (Value Expressions)
在 Script 中,凡是需要值的位置(URL、请求 body、字段值、条件、筛选值、目标 id 等)都是下面三种形态之一。例外只有两个:Regex 的 pattern 与 Cache 的 key 只能写成字面量,其中的 { /pointer } 不会被替换为值。本文档说明这三种形态、值来自何处(上下文根),以及 WEEGLOO 数据特有的 Locale 映射规则。Statement 目录中的所有字段都遵循此规则。
三种形态
| 形态 | 规则 | 示例 |
|---|---|---|
| 引用 (reference) | 在上下文中解析字符串内的 { /json-pointer }。 | "{ /payload/fields/title }" |
| 字面量 (literal) | 不含 { /ptr } 的值(字符串、数字、布尔值、对象、数组)。原样使用。 | "draft", 42, true, { "a": 1 } |
| 运算与条件 (JsonLogic) | 以单个运算符作为键的对象。操作数本身又是值表达式(引用、字面量、嵌套)。运算符是否需要加 $,取决于所在位置。 | { "$+": [ "{ /vars/n }", 1 ] } |
这三种形态可以嵌套:在 JsonLogic 操作数中放入引用,把引用的结果再放入运算,即可这样组合。
数据位置与表达式位置:何时加 $
同一份 JSON 会因所在位置不同而被解读成不同的含义。区分的标准是该位置的键归谁所有。fields 的键是 Content Type 的字段 id,Http.body 的键是对方 API 的 schema,因此在这类位置上,cat 或 in 必须是字段名,而不是运算符。
| 位置 | 对应字段 | 解读方式 |
|---|---|---|
| 数据位置 | 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 的各个字段,以及 Signature·Hash·Regex 的值字段) | 因为是字符串,所以只能放入 { /pointer }。 |
| 仅字面量 | Regex.pattern、Cache.key | 不是值表达式。写在 Regex.pattern 中的 { /pointer } 不会被替换,而会成为 pattern 的一部分。 |
规则只有两条。
- 在数据位置,不带
$的键始终是字段名。 要使用运算,就给运算符加上$。 - 一旦通过
$进入表达式,其内部就全都是表达式。 嵌套的运算符不需要$(加上也可以)。
拿不准时,请给所有运算符都加上
$。 在任何位置都是正确的。
// 数据位置: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 那样,键本身确实需要以 $ 开头时,请把 $ 写两次。"$$ref" 表示数据键 $ref。只有最前面的一个 $ 会被剥掉($$$ref 即 $$ref),而且只对键生效(值里面的 $ 保持原样)。
"body": { "$$ref": "#/components/schemas/Item", "topK": { "$min": [ "{ /payload/fields/k }", 50 ] } }会被拒绝的两种情况
以下两种情况不会被悄悄解读为另一种含义,而是会作为错误被拒绝。
$键与同一对象中的其他键并存时会报错。运算必须是该对象的唯一键,同级数据往外挪一层即可。- 无法识别的
$键会报错。$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 } 的顶层段是下面七种之一。
| 根 | 内容 |
|---|---|
/payload | 调用时传入的 JSON payload(输入)。例如:{ /payload/fields/email } |
/rawPayload | 同一个输入,但保留为调用方发送的 body 字符串本身(解析之前)。例如:{ /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 } |
/error | 仅在 Try 的 catch 块内使用。即捕获到的错误 { message }。例如:{ /error/message } |
除 /<name> 之外的六个名称(payload·rawPayload·headers·now·vars·error)是保留名,不能用作 statement 的 name。用了同名会覆盖那个根,因此在保存时就会被拒绝(公共字段的绑定名规则)。
/rawPayload:发送时的原始 body
/payload 是解析后的值,/rawPayload 是同一个 body 的原文字符串。两者指向同一样东西,却并不相同:把解析后的值重新变成字符串时,空白、数字写法、转义、重复键都会被规范化,无法回到发送时的字节。
因此,在发送的字节之上计算出来的值只能用 /rawPayload 处理。 最典型的就是支付服务商 Webhook 的签名校验(Signature)。平时取值引用则用 /payload。
调用 body 只接受 JSON 对象。body 为空视为没有 body;不是 JSON 对象时(损坏的 JSON、数组、标量、字面量 null)不执行,并予以拒绝(参见错误)。
/now:执行开始的时刻
/now 以三种形态保存本次执行开始的时刻。
| 指针 | 值 |
|---|---|
{ /now/seconds } | epoch 秒(整数) |
{ /now/millis } | epoch 毫秒(整数) |
{ /now/iso } | 与 sys.createdAt 相同的平台时刻表示字符串(UTC) |
- 一次执行只有一个时刻。 它不是读取时钟的 statement,而是在执行开始时埋入的值,所以两个语句不会看到不同的值。
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 } |
ParseJson | 解析后的值本身(对象·数组·标量) | { /quote/items/0/price } |
Signature | Boolean(是否通过校验) | { /verified } |
Hash | 字符串(按所声明表示形式的摘要) | { /expectedSign } |
Regex | Match 为 Boolean。Capture 为数组(0=整个匹配,1 起为捕获组),没有匹配时为 null | { /isOrderId }, { /sig/1 } |
ResourceFind在没有匹配时绑定null。用{ "==": [ "{ /found }", null ] }来分支判断是否存在。ResourceRead(单条)在目标不存在时会报错(可用Try处理)。详情参见 Statement 目录的资源读取。- 读取 ServiceUser 时,结果就是会员资源本身(
{ /member/sys/id })。与 Content·Media 不同,其字段不是 Locale 映射,而是值本身。规则参见读取会员目录。
运算与条件:JsonLogic
需要计算或条件时,使用 jsonlogic.com 规范中的运算符对象。
- 数据访问统一使用
{ /ptr }引用,而非原生的var(dot-path)写法。引擎先解析操作数中的指针,再应用运算符。 - 运算符必须是该对象的唯一键。在数据位置,只有带
$的键才是运算;在表达式位置,无论带不带$都是运算(参见数据位置与表达式位置)。
运算符表
表中的名称是运算符 token。在数据位置使用时要在前面加
$(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。 |
| 算术 | + | 所有操作数之和。 |
| 算术 | - | 一个操作数时取负,两个操作数时做减法。 |
| 算术 | *、/、% | 乘、除、取余。 |
| 聚合 | min、max | 操作数的最小值、最大值。 |
| 字符串 | cat | 把所有操作数拼接成字符串。 |
| 包含 | in | { "in": [needle, haystack] }。haystack 是字符串时判断子字符串,是集合时判断元素包含。 |
| 数组 | merge | 把多个数组或值扁平化为一个数组(用于累积收集)。 |
不支持数组遍历运算符(map、filter、reduce、all、some、none)。Script 通过 Loop 遍历数组(参见 Statement 目录的 Loop)。
数字转换与示例
数字转换规则如下。数字保持原样,true 转为 1,false 转为 0,字符串会被解析(无法解析时计算会失败),null 转为 0。
下面的片段以表达式位置为准。要放进数据位置(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"真假判定 (Truthiness)
if、and、or、!、!! 以及 If.condition、Loop.while 按以下规则判定真假。
- falsy:
null、false、数字0、空字符串""、空集合(空数组)。 - truthy:其余全部(非 0 的数字、非空的字符串和数组、所有对象)。
键也可以引用
像 fields 这样的映射,其键也支持 { /ptr } 引用。键会在运行时解析。
"fields": { "{ /payload/fields/fieldName }": { "en-US": "{ /payload/fields/fieldValue }" } }如果两个键解析为相同的值,就会发生冲突并导致引擎报错。
Locale 映射:Content 与 Media 特有的规则
WEEGLOO Content 与 Media 的每个字段都不是单一值,而是按 Locale 划分的映射(例如 balance 为 { "en-US": 1, "ko-KR": 10 })。因此读写时必须连同 Locale 一起处理。Media 同样,其 title 和 description(标量)、file(摄取指令)都是 Locale 映射。像 /payload 或 HTTP 响应这类并非 Content 或 Media 的 JSON 则与此规则无关(保持 schema 所定义的结构,是标量就是标量)。
读取
- 要取得标量,需连 Locale 一起指定:
{ /<name>/fields/<field>/<locale> }(例如{ /post/fields/title/en-US })。 - 不带 Locale 时,
{ /<name>/fields/<field> }会返回 Locale 映射的整个对象。 localized:false字段只存在于默认 Locale 存储桶中,因此用该默认 Locale 代码来读取。
写入 (ResourceCreate、ResourceUpdate、ResourcePatch 的 fields)
值是按 Locale 划分的映射 { "<locale>": <标量值表达式> }。与读取对称。
"fields": {
"title": { "en-US": "Hello", "ko-KR": "안녕" }, // 多个 Locale 就并列存储桶
"status": { "en-US": "paid" }
}ResourceCreate必须在所有要填充的字段中都包含 Space 默认 Locale 存储桶(default-locale 规则)。ResourceUpdate是整体替换。fields中不存在的字段和 Locale 会被移除(包括 file)。ResourcePatch只更新指定的字段和存储桶(其余字段和 Locale 保持不变)。- 用字面量
null删除:当值为字面量null时,会删除该 (field, locale) 存储桶(这是在 Patch 中清空特定 Locale 的标准做法)。""(空字符串)不是删除,而是设置为空值。值表达式({ /ptr })在运行时求值为 null 时不是删除,而是报错(不会静默吞掉 payload 的缺失)。删除只适用于字面量null。 Media的file:值不是标量,而是摄取指令{ "source": …, "encoding": "url"|"base64" }。摄取实际上做了什么,在 Statement 目录的 ResourceCreate 中讲解。localized:false字段只放入默认 Locale 存储桶。- Locale 代码(映射的键)也可以用
{ /ptr }引用(参见上文 键也可以引用)。用于创建动态 Locale。
locale 便利字段
在 ResourceCreate、ResourceUpdate、ResourcePatch 中提供 locale 时,引擎会自动把 fields 的每个值包装成 { <locale>: 值 } 存储桶。 也就是说只需提供标量即可。
// 下面两个是等价的
{ "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 的同时又在值中嵌套了 Locale 映射({ "en-US": … }),就会变成 { <locale>: { "en-US": … } } 这样的双重嵌套(属于作者失误)。请统一为:用 locale 时只写标量,不用时只写显式的 Locale 映射。
where 与 order 中的 Locale
- 在
where和order中,fields.X的 Space 默认 Locale 会被引擎自动应用(与 CMA 查询一致)。 - 要指定特定的 Locale,请显式写成
fields.X.<locale>。
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } } // 默认 Locale 的 slug
"where": { "fields.title.ko-KR": { "prefix": "안" } } // 特定 Locale错误
以下是违反值表达式规则时出现的错误码。这些检查在保存时进行;违反定义的其他静态约束的错误码在执行语义、约束与安全的错误中,调用时出现的错误码在端点的错误中。所有资源共通的错误码,请参见通用错误。
| 错误码 | 条件 |
|---|---|
WGL400056 | 在数据位置上,$ 运算键与同一对象中的其他键并存。 |
WGL400055 | 在数据位置上写了未被定义为运算符的 $ 键。 |
相关文档
- Statement 目录:使用值表达式的 24 种 statement 的字段与结果。
- 执行语义、约束与安全:执行顺序、错误、乐观锁、静态约束。
- Cookbook:组合值表达式的完整示例。
- Script 概述:顶层结构与一次执行可用的时间。
