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

FormaReglaEjemplo
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ónCampos correspondientesCómo se lee
Posición de datosfields (ResourceCreate, ResourceUpdate, ResourcePatch), Http.body, Return.value, SetVar.value, Cache.value, Cache.defaultValueUna clave sin $ es siempre un nombre de campo. Para usar una operación, se antepone $.
Posición de expresiónIf.condition, Loop.while, versionTodo el valor es una expresión. Como operador valen tanto cat como $cat.
Posición de plantillaTodo 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 literalRegex.pattern, Cache.keyNo 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.

  1. En una posición de datos, una clave sin $ es siempre un nombre de campo. Para usar una operación, anteponga $ al operador.
  2. 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. $catt no 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 null y 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ízContenido
/payloadEl payload JSON (la entrada) que se pasa en la llamada. Ejemplo: { /payload/fields/email }
/rawPayloadContiene esa misma entrada tal cual, como la cadena del cuerpo que envió el llamante (antes del parseo). Ejemplo: { /rawPayload }
/headersLos 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 }
/nowEl 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 }
/errorSe 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.

PunteroValor
{ /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 Parallel tambié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 iso está 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.

StatementForma del resultadoEjemplo 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 }
ParseJsonEl valor parseado en sí (objeto, array, escalar){ /quote/items/0/price }
SignatureBoolean (si la verificación pasa o no){ /verified }
HashCadena (el digest en la notación declarada){ /expectedSign }
RegexCon 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 }
  • ResourceFind vincula null cuando no hay coincidencia. Ramifique según la existencia con { "==": [ "{ /found }", null ] }.
  • ResourceRead (individual) es un error cuando el destino no existe (se puede gestionar con Try). 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 el var vanilla (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 $ (cat pasa a ser $cat). En una posición de expresión valen los dos.

CategoríaOperadorSignificado y ejemplo
Condiciónif (alias ?:){ "if": [cond, entonces, cond2, entonces2, …, predeterminado] }. El valor de la primera condición verdadera; si no hay ninguna, el último predeterminado.
Lógicaand, orEvaluació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ónmin, maxEl mínimo y el máximo de los operandos.
CadenacatConcatena todos los operandos como cadenas.
Pertenenciain{ "in": [needle, haystack] }. Si haystack es una cadena, subcadena; si es una colección, pertenencia de elemento.
ArraymergeAplana 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úmero 0, 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:false está 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" }
}
  • ResourceCreate debe incluir el bucket del locale predeterminado del Space en cada campo que rellena (la regla default-locale).
  • ResourceUpdate es un reemplazo completo. Los campos y locales que no estén en fields se eliminan (incluido el archivo).
  • ResourcePatch actualiza solo los campos y buckets especificados (los demás campos y locales se mantienen).
  • Eliminación con un null literal: cuando el valor es un null literal, 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 un null literal elimina.
  • Media file: 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:false se 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 where y order, para fields.X el 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ífico

Errores

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ódigoCondición
WGL400056En una posición de datos, una clave de operación $ convive con otras claves del mismo objeto.
WGL400055En una posición de datos, la definición usa una clave $ que no está definida como operador.