JSONata in Pantarey
JSONata ist die Abfrage- und Transformationssprache für JSON, mit der Pantarey Daten im Prozess formt, berechnete Felder rechnet und Prüfregeln auswertet. Ein Ausdruck liest aus einem JSON-Objekt und liefert ein neues Ergebnis: ein Feld, ein Objekt, eine Liste oder eine Zahl. Felder stehen ohne Klammern (antrag_db.mitarbeiter), Objekte werden wie JSON geschrieben, Funktionen beginnen mit $. Die vollständige Sprachbeschreibung mit allen Standardfunktionen steht auf jsonata.org; Pantarey verwendet die Bibliothek in Version 2.2.2 im Prozess, im Daten Designer und im Browser. Diese Seite beschreibt, was in Pantarey dazukommt: die Variablen, die Pantarey-Funktionen, die häufigsten Muster, das Verhalten im Fehlerfall und die Wege zum Testen.
Wo JSONata gilt
JSONata gilt überall, wo das ganze Feld ein Ausdruck ist: Input- und Output-Ausdruck jeder Aktivität, Input des Startereignisses, Input und Output des Datenbank-Elements, die JSONata-Felder der Service-Aufgaben (E-Mail-Empfänger, Anhänge, HTML-Dateninput, PDF-Daten, Graustufen-Eingabe), das Formular einer Benutzeraufgabe, berechnete Felder und Prüfregeln mit Sprache JSONata, der Ausdruck des Datensatz-Widgets und die Bedingungen der Planungstafel. Textfelder wie Betreff, Vorgangskennung oder Dateiname nehmen dagegen $(…)-Platzhalter. Die vollständige Zuordnung steht unter Welche Sprache wo?.
Variablen
| Variable | Bedeutung | Wo |
|---|---|---|
$ |
das aktuelle Objekt: im Prozess die Nachricht (Input) oder das Rohergebnis der Aktivität (Output); im Daten Designer der Datensatz oder, in einer Liste, die aktuelle Zeile | überall |
$$ |
die Wurzel der Eingabe, auch innerhalb von Listen und Funktionen | überall |
$root |
der ganze Datensatz, auch wenn $ gerade eine Listenzeile ist |
nur berechnete Felder und Prüfregeln |
$old |
der gespeicherte Vorzustand des Datensatzes, null bei Neuanlage |
nur berechnete Felder |
$$context |
Angaben zum letzten Schritt, von Pantarey in die Nachricht geschrieben | Nachricht im Prozess |
Im Prozess sind $root und $old nicht gesetzt. Ein berechnetes Feld in einer Liste greift mit $root.kunde auf den Datensatz zu, während menge * preis sich auf die Zeile bezieht, siehe Berechnete Felder.
$$context
Pantarey ergänzt die Nachricht nach jedem Schritt um das Objekt $$context. Weil der Name mit $$ beginnt, wird er in JSONata mit Backticks angesprochen: `$$context`.executedBy. In Platzhaltern genügt $($$context.executedBy). Welche Felder enthalten sind, hängt vom Schritt ab:
| Quelle | Felder in $$context |
|---|---|
| Benutzer-Start (Formular) | activityType: "StartEvent", eventDefinition: "UserStartDefinitionType", executedBy (E-Mail), executedAt (ISO-Zeitstempel), selectedAction (wenn der Startknopf eine Aktion trägt) |
| Start per KI-Assistent (MCP) | wie Benutzer-Start, zusätzlich startedVia: "mcp", mcpClient |
| Start per Datei-Upload | wie Benutzer-Start, zusätzlich fileUpload: true; die Nachricht enthält uploadedFile bzw. uploadedFiles |
| Datensatz-Auslöser (Widget Laufende Timer) | wie Benutzer-Start |
| Zeitplan (Timer-Start) | activityType: "StartEvent", eventDefinition: "TimerEventDefinition", sonst nichts |
| Benutzeraufgabe abgeschlossen | activityType: "bpmn:UserTask", finishedAt (Millisekunden), executedBy, executedAt, selectedAction (nur wenn eine Aktionsschaltfläche gedrückt wurde) |
| Benutzeraufgabe per KI-Assistent abgeschlossen | zusätzlich completedVia: "mcp", mcpClient |
| Benutzeraufgabe nach Zeitlimit, Verhalten „fortfahren“ | timedOut: true, completedBy: "timeout", activityType: "userTask" oder "externalUserTask" |
| Externe Aufgabe erledigt | activityType: "externalUserTask", completedAt, completedBy: "external"; die Nachricht enthält außerdem selectedAction und formData |
| Externe Aufgabe asynchron | activityType: "externalUserTask", executionMode: "async", completedAt, completedBy: "async-dispatch" |
| Prozessende | activityType: "bpmn:Process", finishedAt |
| Dry Run | executedBy: "dry-run@pantarey.io" |
Die Vorgangs-ID und die Element-ID stehen nicht in $$context. Wird eine Nachricht mit $$context in einen Datensatz geschrieben, landet das Objekt als Feld im Datensatz; der Input des Datenbank-Elements entfernt es mit $sift($, function($v, $k) { $k != '$$context' }).
Pantarey-Funktionen
Zusätzlich zur JSONata-Standardbibliothek gibt es $fn('name', …) mit Funktionen für Text, Zahlen, Datum, Arbeitstage, Zähler und ULIDs, zum Beispiel $fn('date_toGermanDate', antrag_db.von) oder $fn('nextId', 'rechnung'). Der Katalog steht unter Template-Funktionen $fn. In JSONata erreichen die Argumente die Funktion als echte Werte (eine Zahl bleibt Zahl), in Platzhaltern als Text.
Zwei Einschränkungen: sql_list und sql_raw sind in JSONata nicht erlaubt („fn(): not allowed in jsonata“), sie gehören in SQL-Abfragen. Und bei der Live-Berechnung im Formular (berechnete Felder, Prüfregeln im Browser) werden Ausdrücke mit $fn( übersprungen; sie rechnen erst beim Speichern.
Die alten Schreibweisen $nextCounter('name'), $ulid(), $dateToPath(x) und $toGermanDate(x) sind weiter gültig und werden intern auf $fn umgeleitet.
Zehn Grundmuster
Alle Beispiele sind gegen die genannte Eingabe ausgewertet. Die Eingabe steht für die Nachricht im Prozess „Urlaub beantragen“ bzw. eine Materialbestellung der Musterfirma GmbH.
1. Feld lesen
Eingabe:
{ "antrag_db": { "mitarbeiter": "anna.funk@musterfirma.de", "von": "2026-07-13" } }
Ausdruck: antrag_db.mitarbeiter
Ergebnis: "anna.funk@musterfirma.de"
$.antrag_db.mitarbeiter ist dasselbe. Ein Pfad in ein fehlendes Feld liefert kein Ergebnis, siehe Fehlerverhalten.
2. Objekt bauen
Eingabe wie oben.
{
"to": antrag_db.mitarbeiter,
"subject": "Urlaub " & antrag_db.von
}
Ergebnis:
{ "to": "anna.funk@musterfirma.de", "subject": "Urlaub 2026-07-13" }
Schlüssel stehen in Anführungszeichen, Werte sind Ausdrücke. So entsteht die Eingabe für eine Service-Aufgabe.
3. Felder behalten oder entfernen
Eingabe:
{ "antrag_db": { "von": "2026-07-13", "bis": "2026-07-17", "status": "offen", "$$context": { "executedBy": "anna.funk@musterfirma.de" } } }
Entfernen mit $sift: $sift(antrag_db, function($v, $k) { $k != '$$context' })
Ergebnis: { "von": "2026-07-13", "bis": "2026-07-17", "status": "offen" }
Ergänzen oder überschreiben mit $merge: $merge([antrag_db, { "status": "genehmigt" }])
Ergebnis: { "von": "2026-07-13", "bis": "2026-07-17", "status": "genehmigt", "$$context": { … } }
Das zweite Objekt gewinnt bei gleichen Schlüsseln. Beides zusammen schreibt einen bereinigten Datensatz mit neuem Status.
4. Liste filtern
Eingabe:
{ "positionen": [
{ "artikel": "Monitor 27 Zoll", "menge": 2, "preis": 249 },
{ "artikel": "USB-C-Dock", "menge": 1, "preis": 129 }
] }
Ausdruck: positionen[menge > 1]
Ergebnis: { "artikel": "Monitor 27 Zoll", "menge": 2, "preis": 249 }
Ein Treffer kommt als einzelnes Objekt zurück, nicht als Liste. Soll das Ergebnis immer eine Liste sein, hängt man [] an: positionen[menge > 1][] ergibt [ { "artikel": "Monitor 27 Zoll", … } ].
5. Summe
Eingabe wie in Muster 4.
Ausdruck: $sum(positionen.(menge * preis))
Ergebnis: 627
positionen.(menge * preis) rechnet je Zeile, $sum addiert. Ebenso $max, $min, $average.
6. Bedingung
Eingabe:
{ "antrag_db": { "status": "genehmigt" }, "entscheidung": { "genehmigt": false } }
Ausdruck: antrag_db.status = "genehmigt" ? "Zusage" : "Absage"
Ergebnis: "Zusage"
Mit $exists gegen fehlende Felder absichern: $exists(entscheidung.begruendung) ? entscheidung.begruendung : "ohne Begründung" ergibt "ohne Begründung". Vergleich ist =, nicht ==; ungleich ist !=.
7. Zeichenketten
Eingabe:
{ "rechnung": { "nummer": 1024, "datum": "2026-11-11" } }
Ausdruck: "Rechnung " & rechnung.nummer & " vom " & $fn('date_toGermanDate', rechnung.datum)
Ergebnis: "Rechnung 1024 vom 11.11.2026"
& verbindet und wandelt Zahlen dabei in Text. $string(1024) wandelt ausdrücklich um, $uppercase, $substring, $replace, $split und $join sind weitere Standardfunktionen.
8. Datum
Eingabe:
{ "antrag_db": { "von": "2026-07-13" } }
{
"heute": $substring($now(), 0, 10),
"in_einer_woche": $fromMillis($toMillis(antrag_db.von) + 7 * 86400000, '[Y0001]-[M01]-[D01]'),
"deutsch": $fn('date_toGermanDate', antrag_db.von)
}
Ergebnis am 11.11.2026:
{ "heute": "2026-11-11", "in_einer_woche": "2026-07-20", "deutsch": "13.07.2026" }
$now() liefert den ISO-Zeitstempel in UTC. Arbeitstage zählt $fn('date_workdays', von, bis) mit dem Arbeitskalender des Kontos.
9. Zählen
Eingabe wie in Muster 4.
Ausdruck: { "anzahl": $count(positionen), "gross": $count(positionen[menge > 1]), "leer": $count(nicht_da) }
Ergebnis: { "anzahl": 2, "gross": 1, "leer": 0 }
$count auf ein fehlendes Feld ergibt 0, kein Fehler. Deshalb eignet es sich für Bedingungen wie $count(positionen) > 0.
10. Dateien weiterreichen
Eingabe:
{ "rechnung": { "nummer": 1024 }, "rechnung_pdf": { "referenceId": "01HX7Z8K79G5T0ZJWBJ6C3V8PF", "filename": "document.pdf", "contentType": "application/pdf" } }
{
"referenceId": rechnung_pdf.referenceId,
"filename": "Rechnung_" & rechnung.nummer & ".pdf",
"contentType": rechnung_pdf.contentType
}
Ergebnis:
{ "referenceId": "01HX7Z8K79G5T0ZJWBJ6C3V8PF", "filename": "Rechnung_1024.pdf", "contentType": "application/pdf" }
Eine Datei ist in der Nachricht ein Verweis mit referenceId, filename und contentType, Bilder zusätzlich mit thumbnail. JSONata behandelt ihn wie jedes Objekt; der Verweis kann unverändert weitergegeben (rechnung_pdf) oder wie hier mit neuem Namen gebaut werden. E-Mail-Anhänge verwerfen alles ohne referenceId.
Fehlerverhalten
Ein leeres Feld bedeutet: keine Transformation, die Daten gehen unverändert weiter. Liefert ein Ausdruck kein Ergebnis (in JSONata undefined, etwa bei einem Pfad in ein fehlendes Feld oder einem Filter ohne Treffer), meldet Pantarey:
Der Ausdruck liefert kein Ergebnis. Häufige Ursachen: das referenzierte Feld fehlt im aktuellen Datensatz, Tippfehler im Feldpfad (Groß-/Kleinschreibung beachten) oder ein Filter liefert keinen Treffer. Ausdruck: '…'. Verfügbare Felder im Datensatz: …
null ist dagegen ein gültiges Ergebnis. Was danach passiert, hängt vom Ort ab:
| Ort | Folge |
|---|---|
| Input-Ausdruck | Der Schritt scheitert mit „Transformation (Input-Parameter) für Aktivität '…' fehlgeschlagen“. Pantarey speichert einen Wiederaufsetzpunkt mit der Nachricht vor der Transformation; im Process Explorer lässt sich die Nachricht anpassen und der Vorgang ab dieser Aktivität fortsetzen. |
| Output-Ausdruck | Der Schritt scheitert mit RESULT_PARAMETER_ERROR, ebenfalls mit Wiederaufsetzpunkt und einem Vorschlag zum Fortfahren. Am Endereignis ist der Vorgang fehlgeschlagen. |
| Prüfregel (JSONata) | Ein Auswertungsfehler, auch „kein Ergebnis“, lässt das Speichern scheitern („Validation rule … failed to evaluate“). Bei der Live-Prüfung im Formular wird ein Ausdruck ohne Ergebnis übersprungen. Deshalb Felder mit $exists absichern, siehe Prüfregeln. |
| Berechnetes Feld (JSONata) | Das Speichern scheitert mit „Computed field … failed“. Ein Ergebnis null entfernt das Feld aus dem Datensatz. |
| E-Mail: Empfänger, Absender, Antwort an | Schlägt JSONata fehl und der Text sieht wie eine Adresse aus, gilt der Text wörtlich; sonst scheitert der Schritt. Anhänge und HTML-Dateninput scheitern immer. |
| Gateway | Trifft keine Bedingung zu und es gibt keinen Default-Pfad, scheitert der Schritt mit XOR_GATEWAY_ERROR und einer Liste aller Bedingungen und ihrer Ergebnisse. |
Sicherheit: Zugriffe auf JavaScript-Interna ($.constructor, __proto__) liefern kein Objekt; die Bibliothek ist deshalb auf 2.2.2 festgelegt.
Grenzen
- Input und Ergebnis des Startereignisses dürfen je 50 KB Text nicht überschreiten; die Validierung blockiert sonst den Deploy. Große Nachrichten im weiteren Prozess sind kein Problem, Pantarey lagert sie automatisch aus.
- Ein Prozessschritt hat 360 Sekunden; eine Gateway-Bedingung 15 Sekunden. Ein JSONata-Ausdruck, der länger rechnet, lässt den Schritt scheitern. Rechenintensive Verarbeitung gehört in eine Service-Aufgabe.
- Für Schleifen gilt: ab 200 KB Listendaten wechselt Pantarey in den verteilten Modus; die Grenzen der Durchläufe stehen unter Schleifen.
Testen
Live-Vorschau im JSONata-Editor. Im Dialog Daten & Variablen konfigurieren öffnet der Knopf mit dem Code-Symbol (Tooltip JSONata-Editor) neben dem Input- bzw. Output-Feld den Editor; in den Einstellungsdialogen der Service-Aufgaben (PDF-Daten-Mapping, E-Mail-Felder) heißt der Knopf Erweiterten Editor mit Live-Vorschau öffnen. Der Dialog Input-Parameter (JSONata) (bzw. Output) zeigt links Beispiel-Eingabedaten als editierbares JSON, rechts den JSONata-Ausdruck und darunter die Live-Vorschau, die nach jeder Änderung neu rechnet. Die Beispieldaten sind mit einem festen Beispiel (auftrag, kunde, positionen) vorbelegt und werden durch die eigene Nachricht ersetzt, etwa aus ⋮ → Nachrichtenschema oder aus Aktivitäten → In/Out eines Vorgangs; nur beim PDF-Daten-Mapping kommen sie aus den PDF-Beispieldaten. Ungültiges JSON meldet „Beispieldaten konnten nicht in gültiges JSON konvertiert werden.“ Übernehmen schreibt den Ausdruck ins Feld, Abbrechen verwirft.
Die Vorschau rechnet im Browser und ersetzt Backend-Funktionen durch Attrappen: $fn-Funktionen außer sql_* sind verfügbar, sys_ulid liefert immer denselben Wert, Zähler zählen nur im Browser hoch und verbrauchen keine Nummer, Arbeitstage kennen nur die bundesweiten Feiertage. {{value:…}} wird vor der Auswertung eingesetzt. Bei Dateiname und Vorgangskennung gibt es keinen Editor, weil dort Platzhalter gelten.
Dry Run. Der Dry Run im Prozess Designer spielt den Prozess mit einer Startnachricht aus dem Datenschema des Startformulars durch und prüft dabei Input, Output, Ergebnispfad, Vorgangskennung, Anzeigetext und Zuweisung jedes Elements. Zähler werden nur gelesen. Gateway-Bedingungen erkennt der Dry Run nur als wörtliches true oder false, alles andere nimmt den Default-Pfad.
KI-Assistent. Mit der Rolle Bauen steht dem KI-Assistenten das Werkzeug test_jsonata zur Verfügung: Ausdruck (bis 8000 Zeichen), Beispieldaten sowie optional root und old. Ein Ausdruck ohne Ergebnis kommt als Warnung zurück, ein Syntaxfehler mit Position und Token. Alle Beispiele dieser Seite sind so geprüft.
Beispiel
„Absage senden“ im Prozess „Urlaub beantragen“ der Musterfirma GmbH hat den Input-Ausdruck
{ "subject": "Urlaubsantrag abgelehnt", "contentTemplate": "<p>Der Urlaubsantrag vom {{von}} bis {{bis}} wurde abgelehnt.</p>", "contentData": { "von": $fn('date_toGermanDate', antrag_db.von), "bis": $fn('date_toGermanDate', antrag_db.bis) }, "toRecipients": [antrag_db.mitarbeiter] }. Im JSONata-Editor stehen linksantrag_dbundentscheidungaus dem Nachrichtenschema, rechts zeigt die Live-Vorschau"von": "13.07.2026"und die Empfängerliste. Fehltantrag_db.bisin den Beispieldaten, meldet die Vorschau „Der Ausdruck liefert kein Ergebnis“ und nennt die vorhandenen Felder.
Häufige Fragen
Warum liefert mein Ausdruck „kein Ergebnis“?
Meist zeigt ein Pfad auf ein Feld, das in dieser Nachricht nicht existiert: Tippfehler, Groß-/Kleinschreibung, oder das Ergebnis eines früheren Schritts liegt unter einem anderen Ergebnispfad. Die Meldung nennt die vorhandenen Felder; ⋮ → Nachrichtenschema zeigt die Namen je Schritt. Optionales mit $exists(feld) ? feld : "" absichern.
Mein Feld heißt „Rechnungs-Nr: intern“. Wie spreche ich es an?
Namen mit Doppelpunkt, Leerzeichen, Bindestrich oder führendem $ stehen in Backticks: `Rechnungs-Nr: intern` oder `$$context`.executedBy.
Warum bekomme ich ein Objekt statt einer Liste mit einem Element?
JSONata gibt eine Liste mit genau einem Treffer als einzelnes Objekt zurück. positionen[menge > 1][] erzwingt eine Liste; $count(…) zählt in beiden Fällen richtig.
Zahl oder Text: warum scheitert betrag + 1?
Formularfelder vom Typ Text liefern "19.99" als Zeichenkette; + verlangt Zahlen („The left side of the + operator must evaluate to a number“). $number(betrag) + 1 rechnet, $string(1024) wandelt umgekehrt in Text, $type(feld) zeigt den Typ.
Nächster Schritt
Template-Funktionen
$fn: der Katalog der Pantarey-Funktionen für Text, Zahlen, Datum, Zähler und SQL.