Expresiones de valor (Value Expressions)
Cada lugar de un Script donde se necesita un valor (una URL, un body de petición, un valor de campo, una condición, un valor de filtro, un id de destino, etc.) adopta una de las tres formas siguientes. Solo hay dos excepciones: el pattern de Regex y la key de Cache se escriben únicamente como literales y los { /pointer } que contengan no se convierten en valores. Este documento describe esas tres formas, de dónde vienen los valores (las raíces de contexto) y las reglas de mapa de locales específicas de los datos de WEEGLOO. Todos los campos del Catálogo de statements siguen estas reglas.
Las tres formas
| Forma | Regla | Ejemplo |
|---|---|---|
| Referencia (reference) | Resuelve un { /json-pointer } dentro de una cadena contra el contexto. | "{ /payload/fields/title }" |
| Literal (literal) | Un valor sin { /ptr } (una cadena, número, booleano, objeto o array). Se usa tal cual. | "draft", 42, true, { "a": 1 } |
| Operaciones y condiciones (JsonLogic) | Un objeto cuya única clave es un operador. Sus operandos son a su vez expresiones de valor (referencias, literales o anidamientos). Según la posición, el operador necesita $. | { "$+": [ "{ /vars/n }", 1 ] } |
Las tres formas se anidan: se coloca una referencia en un operando de JsonLogic y el resultado de esa referencia se introduce a su vez en otra operación.
Posición de datos y posición de expresión: cuándo poner $
El mismo JSON se lee de forma distinta según la posición. El criterio que las separa es quién es el dueño de las claves de esa posición. Las claves de fields son los id de campo de un Content Type y las claves de Http.body son el esquema de la API remota, así que en esas posiciones cat o in deben ser nombres de campo, no operadores.
| Posición | Campos correspondientes | Cómo se lee |
|---|---|---|
| Posición de datos | fields (ResourceCreate, ResourceUpdate, ResourcePatch), Http.body, Return.value, SetVar.value, Cache.value, Cache.defaultValue | Una clave sin $ es siempre un nombre de campo. Para usar una operación, se antepone $. |
| Posición de expresión | If.condition, Loop.while, version | Todo el valor es una expresión. Como operador valen tanto cat como $cat. |
| Posición de plantilla | Todo lo demás (url, method, headers[].value, locale, order, over, target.sys.id, los campos de EmailSend, los campos de valor de Signature, Hash y Regex) | Al ser una cadena, solo admite { /pointer }. |
| Solo literal | Regex.pattern, Cache.key | No es una expresión de valor. Un { /pointer } escrito en Regex.pattern no se sustituye: pasa a formar parte del patrón. |
La regla cabe en dos líneas.
- En una posición de datos, una clave sin
$es siempre un nombre de campo. Para usar una operación, anteponga$al operador. - Una vez que se entra en una expresión con
$, todo lo de dentro es expresión. Los operadores anidados no necesitan$(aunque se puede poner).
Si tiene dudas, anteponga
$a todos los operadores. Es correcto en cualquier posición.
// posición de datos: cat es un nombre de campo del Content Type (no la operación de concatenación)
"fields": { "cat": { "en-US": "hello" } }
// cálculo en una posición de datos: $ solo en la frontera; dentro, tal cual
"fields": { "tier": { "en-US": { "$if": [ { ">=": [ "{ /p/score }", 700 ] }, "gold", "silver" ] } } }
// posición de expresión: se escribe tal cual
"condition": { "and": [ { "<": [ "{ /a/body/risk }", 0.5 ] }, { ">=": [ "{ /b/body/score }", 700 ] } ] }Cuando se necesita un nombre de campo que empiece por $: $$
Cuando la clave debe empezar realmente por $, como el $ref o el $schema de JSON Schema, se escribe $ dos veces. "$$ref" significa la clave de datos $ref. Solo se quita un $ inicial ($$$ref es $$ref) y se aplica solo a las claves (el $ dentro de un valor se queda tal cual).
"body": { "$$ref": "#/components/schemas/Item", "topK": { "$min": [ "{ /payload/fields/k }", 50 ] } }Los dos casos que se rechazan
Los dos casos siguientes no se interpretan en silencio con otro significado, sino que se rechazan como error.
- Una clave
$junto a otras claves del mismo objeto es un error. La operación debe ser la única clave de ese objeto; los datos hermanos se sacan un nivel hacia fuera. - Una clave
$desconocida es un error.$cattno es un campo llamado$catt. El espacio de nombres$está reservado para los operadores.
En una posición de expresión también es un error que un nombre de operador conviva con claves hermanas ({ "and": […], "or": […] }). En esa posición no existe la lectura como datos y todo objeto se evalúa como verdadero, así que dejarlo así haría que la condición fuera siempre verdadera en silencio.
Referencia: { /json-pointer }
Dentro de las llaves se coloca un JSON Pointer de RFC 6901 (que debe empezar por /). Se permiten espacios en blanco alrededor de las llaves ({ /a/b } es igual que {/a/b}).
Pointer único frente a plantilla mixta: reglas de tipo
- Cuando toda la cadena es un único pointer, el valor conserva su tipo original (si es número, número; si es objeto, objeto; si es array, array).
- Cuando se mezcla con texto literal, el resultado es concatenación de cadenas (concatenation).
"{ /payload/fields/count }" // si es un valor numérico, número tal cual (p. ej. 42)
"{ /payload/fields/tags }" // si es un array, array tal cual
"page-{ /payload/fields/n }-of-10" // concatenación de cadenas → "page-42-of-10"
"Bearer { /payload/fields/token }" // concatenación de cadenas → "Bearer abc123"Valores ausentes
- Cuando la ruta no existe o el valor está vacío, un pointer único se convierte en
nully una plantilla mixta se convierte en una cadena vacía.
Raíces de contexto: de dónde vienen los valores
El segmento de nivel superior de un { /pointer } es uno de los siete siguientes.
| Raíz | Contenido |
|---|---|
/payload | El payload JSON (la entrada) que se pasa en la llamada. Ejemplo: { /payload/fields/email } |
/rawPayload | Contiene esa misma entrada tal cual, como la cadena del cuerpo que envió el llamante (antes del parseo). Ejemplo: { /rawPayload } |
/headers | Los headers HTTP de la petición que se pasan en la llamada. Las claves están en minúsculas y hay un único valor por nombre. Ejemplo: { /headers/authorization } |
/now | El instante en que arrancó la ejecución. { /now/seconds }, { /now/millis }, { /now/iso } |
/<name> | El resultado de un statement anterior que lleva ese name. Ejemplo: { /order/sys/id } |
/vars/<name> | Una variable mutable de ámbito de script declarada con SetVar. Ejemplo: { /vars/total } |
/error | Se usa solo dentro del bloque catch de un Try. El error capturado, { message }. Ejemplo: { /error/message } |
Los seis nombres que quedan al descontar /<name> (payload, rawPayload, headers, now, vars, error) están reservados y no se pueden usar como name de un statement. Usar uno de ellos sobrescribiría esa raíz, así que se rechaza en el momento de guardar (reglas del nombre de vinculación en los campos comunes).
/rawPayload: el cuerpo tal cual se envió
/payload es el valor parseado y /rawPayload es la cadena original de ese mismo cuerpo. Ambos apuntan a lo mismo, pero no son iguales. Al volver a convertir en cadena el valor parseado, los espacios en blanco, la notación de los números, los escapes y las claves duplicadas quedan normalizados, y ya no se recuperan los bytes enviados.
Por eso, un valor que se calcula sobre los bytes enviados solo se puede tratar con /rawPayload. El caso representativo es la verificación de la firma del webhook de una pasarela de pago (Signature). Las referencias habituales, cuando lo que se quiere es extraer un valor, se hacen con /payload.
El cuerpo de la llamada solo admite objetos JSON. Si el cuerpo viene vacío, se considera que no hay cuerpo; si no es un objeto JSON (JSON malformado, un array, un escalar o un null literal), no se ejecuta y se rechaza (véase Errores).
/now: el instante en que arrancó la ejecución
/now contiene el instante en que arrancó esta ejecución en tres formas.
| Puntero | Valor |
|---|---|
{ /now/seconds } | Segundos de epoch (entero) |
{ /now/millis } | Milisegundos de epoch (entero) |
{ /now/iso } | La cadena de instante de la plataforma, en la misma notación que sys.createdAt (UTC) |
- Una ejecución tiene un único instante. No es un statement que lea el reloj, sino un valor que se fija al arrancar la ejecución, así que dos statements nunca verán valores distintos. Cada rama de un
Paralleltambién hereda ese mismo instante. Al no ser un statement, tampoco cuenta para el número de statements. - No hay ningún campo para elegir la zona horaria. Los valores de epoch son el mismo número en cualquier parte, e
isoestá en notación UTC. - Se usa para verificar la replay window de un webhook (cuántos segundos hace, respecto a ahora, que se emitió la marca de tiempo incluida en la firma). La marca de tiempo suele llegar como cadena, pero las operaciones aritméticas la convierten en número, así que se compara tal cual.
// ¿La marca de tiempo incluida en la firma está dentro de 5 minutos (300 segundos)?
{ "<": [ { "-": [ "{ /now/seconds }", "{ /sig/1 }" ] }, 300 ] }La forma del resultado de un statement
La forma del resultado de un statement que lleva un name varía según el tipo.
| Statement | Forma del resultado | Ejemplo de referencia |
|---|---|---|
Http | { status, body } | { /resp/status }, { /resp/body/choices/0/message/content } |
ResourceCreate, ResourceRead (individual), ResourceFind (individual) | El recurso en sí | { /post/sys/id }, { /post/fields/title/en-US } |
ResourceForEach | (durante el recorrido) name es el elemento actual = el recurso en sí. Solo se referencia dentro de onEach | { /post/sys/id }, { /post/fields/title/en-US } |
ParseJson | El valor parseado en sí (objeto, array, escalar) | { /quote/items/0/price } |
Signature | Boolean (si la verificación pasa o no) | { /verified } |
Hash | Cadena (el digest en la notación declarada) | { /expectedSign } |
Regex | Con Match, Boolean. Con Capture, un array (0 = la coincidencia completa, y desde 1, los grupos de captura) o null si no hay coincidencia | { /isOrderId }, { /sig/1 } |
ResourceFindvinculanullcuando no hay coincidencia. Ramifique según la existencia con{ "==": [ "{ /found }", null ] }.ResourceRead(individual) es un error cuando el destino no existe (se puede gestionar conTry). Se explica en detalle en Lectura de recursos en el Catálogo de Statements.- Al leer un ServiceUser, el resultado es el recurso de miembro en sí (
{ /member/sys/id }). A diferencia de Content y Media, sus campos no son un mapa de locales, sino el valor tal cual. Las reglas se tratan en Lectura del directorio de miembros.
Operaciones y condiciones: JsonLogic
Cuando necesite un cálculo o una condición, use un objeto de operador de la especificación de jsonlogic.com.
- El acceso a los datos se unifica con referencias
{ /ptr }, no con elvarvanilla (dot-path). El motor resuelve primero los pointers de los operandos y luego aplica el operador. - El operador debe ser la única clave de ese objeto. En una posición de datos, solo las claves con
$son operaciones; en una posición de expresión, son operaciones haya o no$(véase Posición de datos y posición de expresión).
Tabla de operadores
Los nombres de la tabla son tokens de operador. Al usarlos en una posición de datos se les antepone
$(catpasa a ser$cat). En una posición de expresión valen los dos.
| Categoría | Operador | Significado y ejemplo |
|---|---|---|
| Condición | if (alias ?:) | { "if": [cond, entonces, cond2, entonces2, …, predeterminado] }. El valor de la primera condición verdadera; si no hay ninguna, el último predeterminado. |
| Lógica | and, or | Evaluación en cortocircuito. and devuelve el primer operando falsy (o el último), or el primer operando truthy (o el último), como valor. |
| Lógica | ! (not), !! (to-bool) | { "!": x } niega el truthy, { "!!": x } indica si es truthy. Para comprobar la existencia se usa a menudo !!. |
| Igualdad | ==, != | Comparación laxa (compara tras la conversión numérica forzada; "1"==1 es verdadero). |
| Igualdad | ===, !== | Comparación estricta (incluye el tipo). |
| Comparación | <, <=, >, >= | Encadenable: { "<": [1,2,3] } es 1<2 AND 2<3. Si un valor no se puede convertir a número (NaN), es false. |
| Aritmética | + | La suma de todos los operandos. |
| Aritmética | - | Con un operando, negación; con dos, resta. |
| Aritmética | *, /, % | Multiplicación, división, resto. |
| Agregación | min, max | El mínimo y el máximo de los operandos. |
| Cadena | cat | Concatena todos los operandos como cadenas. |
| Pertenencia | in | { "in": [needle, haystack] }. Si haystack es una cadena, subcadena; si es una colección, pertenencia de elemento. |
| Array | merge | Aplana varios arrays o valores en un único array (se usa para acumulación). |
Los operadores de iteración de arrays (map, filter, reduce, all, some, none) no se admiten. Script recorre los arrays con Loop (Loop en el Catálogo de Statements).
Conversión numérica y ejemplos
Las reglas de conversión numérica son las siguientes. Un número se deja tal cual, true se convierte en 1, false en 0, una cadena se parsea (si no se puede parsear, el cálculo falla) y null se convierte en 0.
Los fragmentos siguientes se escriben para una posición de expresión. Al colocarlos en una posición de datos (fields, Http.body, Return.value, SetVar.value), anteponga $ al operador de nivel superior y deje los operandos internos tal cual.
{ "-": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } // saldo - coste
{ "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } // saldo < coste → boolean
{ "and": [ { "<": [ "{ /a/body/risk }", 0.5 ] }, { ">=": [ "{ /b/body/score }", 700 ] } ] }
{ "cat": [ "id-", "{ /payload/sys/id }" ] } // "id-<uuid>"
{ "!!": "{ /found/sys/id }" } // true si existe
{ "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } // acumulación en array: SetVar.value es posición de datos, de ahí el $
{ "if": [ "{ /payload/fields/next }", "{ /payload/fields/next }", "END" ] } // next si existe, si no "END"Evaluación de verdadero y falso (Truthiness)
if, and, or, !, !!, junto con If.condition y Loop.while, determinan verdadero y falso según las reglas siguientes.
- falsy:
null,false, el número0, la cadena vacía"", una colección vacía (un array vacío). - truthy: todo lo demás (números distintos de 0, cadenas y arrays no vacíos, y todos los objetos).
Las claves también pueden ser referencias
Las claves de un mapa como fields también admiten referencias { /ptr }. La clave se resuelve en tiempo de ejecución.
"fields": { "{ /payload/fields/fieldName }": { "en-US": "{ /payload/fields/fieldValue }" } }Si dos claves se resuelven al mismo valor, colisionan y es un error del motor.
Mapa de locales: reglas específicas de Content y Media
En WEEGLOO, cada campo de un Content o Media no es un valor sino un mapa por locale (por ejemplo, balance es { "en-US": 1, "ko-KR": 10 }). Por eso, al leer y escribir hay que gestionar el locale de forma conjunta. En Media también, title y description (escalares) y file (la instrucción de ingesta) son mapas de locales. El JSON que no es Content ni Media, como /payload o una respuesta HTTP, no se ve afectado por esta regla (mantiene la estructura que define el esquema, y un escalar sigue siendo un escalar).
Lectura
- Para obtener un escalar, especifique hasta el locale:
{ /<name>/fields/<field>/<locale> }(por ejemplo,{ /post/fields/title/en-US }). - Sin locale,
{ /<name>/fields/<field> }devuelve el objeto completo del mapa de locales. - Un campo
localized:falseestá solo en el bucket del locale predeterminado, así que se lee con el código de ese locale predeterminado.
Escritura (los fields de ResourceCreate, ResourceUpdate, ResourcePatch)
El valor es un mapa de locales, { "<locale>": <expresión de valor escalar> }. Es simétrico con la lectura.
"fields": {
"title": { "en-US": "Hello", "ko-KR": "안녕" }, // enumera buckets para varios locales
"status": { "en-US": "paid" }
}ResourceCreatedebe incluir el bucket del locale predeterminado del Space en cada campo que rellena (la regla default-locale).ResourceUpdatees un reemplazo completo. Los campos y locales que no estén enfieldsse eliminan (incluido el archivo).ResourcePatchactualiza solo los campos y buckets especificados (los demás campos y locales se mantienen).- Eliminación con un
nullliteral: cuando el valor es unnullliteral, se elimina ese bucket (field, locale) (la forma estándar de vaciar un locale concreto en un Patch).""(una cadena vacía) no es una eliminación, sino que establece un valor vacío. Cuando una expresión de valor ({ /ptr }) se evalúa como null en tiempo de ejecución, no es una eliminación sino un error (un payload ausente no se traga en silencio). Solo unnullliteral elimina. Mediafile: el valor no es un escalar sino una instrucción de ingesta,{ "source": …, "encoding": "url"|"base64" }. Lo que hace realmente la ingesta se trata en ResourceCreate en el Catálogo de Statements.- Un campo
localized:falsese coloca solo en el bucket del locale predeterminado. - El código de locale (la clave del mapa) también puede ser una referencia
{ /ptr }(véase arriba Las claves también pueden ser referencias). Se usa para crear locales dinámicos.
Campo de conveniencia locale
Si se da locale a ResourceCreate, ResourceUpdate o ResourcePatch, el motor envuelve automáticamente cada valor de fields en un bucket { <locale>: valor }. Es decir, basta con dar escalares.
// los dos siguientes son equivalentes
{ "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" } } }Si se da locale y el valor ya anida un mapa de locales ({ "en-US": … }), se produce un anidamiento doble como { <locale>: { "en-US": … } } (error del autor). Unifique en un solo estilo: con locale, solo escalares; sin él, solo mapas de locales explícitos.
El locale en where y order
- En
whereyorder, parafields.Xel motor aplica automáticamente el locale predeterminado del Space (igual que una consulta de CMA). - Para indicar un locale específico, especifíquelo como
fields.X.<locale>.
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } } // slug del locale predeterminado
"where": { "fields.title.ko-KR": { "prefix": "안" } } // locale específicoErrores
Códigos que aparecen cuando se incumplen las reglas de las expresiones de valor. Se comprueban al guardar; los códigos por incumplir las demás restricciones estáticas de la definición están en los errores de Semántica de ejecución, restricciones y seguridad, y los códigos que aparecen al invocar, en los errores de los endpoints. Para los códigos comunes a todos los recursos, consulte Errores comunes.
| Código | Condición |
|---|---|
WGL400056 | En una posición de datos, una clave de operación $ convive con otras claves del mismo objeto. |
WGL400055 | En una posición de datos, la definición usa una clave $ que no está definida como operador. |
Documentos relacionados
- Catálogo de statements: Los campos y resultados de los 24 tipos de statement que usan expresiones de valor.
- Semántica de ejecución, restricciones y seguridad: Orden de ejecución, errores, bloqueo optimista y restricciones estáticas.
- Cookbook: Ejemplos completos que combinan expresiones de valor.
- Descripción general de Script: La estructura de nivel superior y el tiempo que recibe una ejecución.
