Skip to main content

bx:json

Mit dem Tag bx:json können JSON Strukturen ausgelesen und einfach durchgegangen werden. Um schnell in tief verschachtelte Objekte zu gelangen, kann JsonPath genutzt werden.

Funktionen:

JSON laden

<bx:json data="[1, 2, 3]">...</bx:json>
<bx:json data="clipboard:json_data">...</bx:json>
<bx:json url="http://server/..." [encoding="utf-8"]>...</bx:json>

Das JSON-Dokument kann entweder direkt als Text übergeben werden (wobei der Umweg über ein Clipboard oder Attribut möglich ist), es kann aber auch eine URL angegeben werden, von der die Daten geladen werden (encoding gibt dabei an, mit welchem Zeichensatz die Daten eingelesen werden, Standard ist utf-8).

Im URL-Modus sendet das JSON-Tag nicht das Session-Cookie mit, da meist externe URLs aufgerufen werden und die SESSIONID nicht ausversehen preisgegeben werden soll.Falls die Daten von einem internen, geschützten Menüpunkt geladen werden (auf den der aktuell eingeloggte Benutzer Zugriff hat), kann `bx:jspinclude` verwendet werden - dieses schickt das Session-Cookie mit: /intern/api.json ...

Der JSON-Parser akzeptiert auch nicht RFC-konformeres JSON, also z.B. Kommentare oder Keys, die nicht mit Anführungszeichen umgeben sind (Details siehe hier).

Ist in den JSON-Daten ein Objekt kodiert, kann dieses mit geschachtelten Json-Tags durchgegangen werden; ein Array verhält sich wie eine Schleife; andere Datentypen haben ihre entsprechenden Funktionen (s.u.).

Falls die JSON selbst generiert wird, sollte zwecks Interoperabilität (z.B. Verwendung in JavaScript direkt) ein Objekt kodiert werden.Falls man nur einen String oder eine Zahl hat, kann man diese(n) einfach im Objekt unter einem Key angeben, also anstatt: 523 besser: { "data": 523 }

Eine fortgeschrittenere Möglichkeit des Daten-Ladens, kann über das Attribut `application` realisiert werden. Hier gibt man den Name eines Application-Attributes an, in dem ein `JsonElement` gespeichert ist. Dieses wird dann vom Tag benutzt und es entfällt das String-Parsen. Dies ist nützlich bei Caches, die man global in der Application vorhalten kann, und die sich nicht oft ändern. Ein `JsonElement` kann mittels `Gson` und der `toJsonTree` Methode erzeugt werden (z.B. aus einer Map, die man mit Groovy zusammenbaut).

JSON durchgehen

Die JSON-Struktur kann einfach via Tags durchgegangen werden:

<bx:json data="..."> <!-- Root-Tag -->
  <bx:json.Kunde> <!-- Sub-Tag -->
    Der Kunde heißt: <bx:json.Vorname /> <bx:json.Nachname />
    <bx:Json field="address home">
      Er wohnt in: <bx:json.Ort />
    </bx:Json>
  </bx:json.Kunde>
</bx:json>

Es ist jeweils die Angabe von encode=... möglich, um z.B. Ausgaben für URLs oder CSV zu encoden.

Das auszugebende Feld kann entweder im Tag-Titel (nach bx:json., also im Beispiel "Kunde") oder im Parameter field="" (oben z.B. "address home") angegeben werden. Die Angabe via field ist nötig, falls der Feldname Sonderzeichen / Leerzeichen enthält.

Ist das zu parsende JSON kein Objekt oder Array, sondern z.B. ein String oder eine Zahl, kann das Root-Element direkt ausgegeben werden:

<bx:json data="12345" /> <!-- gibt 12345 aus -->
<bx:json data="'testing'" contains="test" true="Pass" false="Fail" /> <!-- gibt Pass aus -->

Wird versucht auf ein Feld zuzugreifen, das nicht existiert, wird nichts ausgegeben. Es kann alternativ via dummy ein Alternativwert (im JSON-Format) angegeben werden, der stattdessen genommen wird. Je nach Typ des Wertes in dummy (wird wie ein neues JSON geparst), stehen die Typ-spezifischen Funktionen (s.u.) zur Verfügung.

<!-- falls es kein Feld missingNumber gibt, wird automatisch der Wert 5 genommen -->
<bx:json.missingNumber dummy="5" lt="10">...</bx:json.missingNumber>
 
<!-- es können auch komplexe Sachen als Dummy spezifiziert werden, hier empfiehlt sich ein Clipboard -->
<bx:clipboard.cut name="objdummy">
{
  x: 5,
  s: ""
}
</bx:clipboard.cut>
<bx:json.missingObject dummy="clipboard:objdummy">...</bx:json.missingObject>

Da JSON-Tags (sowohl Root-Tags als auch Sub-Tags von Objekten oder Arrays) mehrfach verschachtelt sein können und man manchmal Sachen von Über-Über-...-Elementen ausgeben will, kann man via Tag-Titel oder name die Tags benennen und via base diese referenzieren (der Parameter name hat beim Suchen des passenden Parent-Tags Vorrang vor dem Tag-Titel). Das entspricht der Funktionalität von baseloop bei Containern. Mittels base="$" kann das Root-Tag referenziert werden (das Tag, bei dem die Daten geladen wurden). Die Angabe von base kann immer zusätzlich zu allen anderen Parametern erfolgen (z.B. können base und path gleichzeitig benutzt werden).

<bx:json data="..."> <!-- Root-Tag -->
  <bx:json.Sub>
    <bx:json.SubSub name="Kunde">
      <bx:json.SubSubSub>
        <bx:json.Feld base="Sub">
          <!-- selber Kontext, als wenn man direkt innerhalb von <bx:json.Sub> wäre -->
        </bx:json.Feld>
        <bx:json.Feld base="Kunde">
          <!-- selber Kontext, als wenn man direkt innerhalb von <bx:json.SubSub> wäre -->
        </bx:json.Feld>
        <bx:json.Feld base="$">
          <!-- selber Kontext, als wenn man direkt innerhalb des Root-Tags wäre -->
        </bx:json.Feld>
      </bx:json.SubSubSub>
    </bx:json.SubSub>
  </bx:json.Sub>
</bx:json>

Je nach Datentyp ergeben sich unterschiedliche Funktionen, die benutzt werden können:

Null

Der Inhalt wird ausgeführt (oder nicht bei not), falls im JSON der Spezialwert null steht. Nicht vorhandene Elemente können so auch abgefragt werden (nicht vorhanden entspricht hier null).

<bx:json.Feld null>Feld war null oder nicht vorhanden</bx:json.Feld>
<bx:json.Feld null not>Feld war vorhanden und nicht null</bx:json.Feld>

Boolean

Boolesche Werte können ähnlich wie Häkchenfelder im Container abgefragt werden. Eine kurze Version (geschlossenes Tag) mit truefalse steht auch zur Verfügung.

<bx:json.Condition [not]>
  ...
</bx:json.Condition>
 
<bx:json.Condition [true=""] [false=""] />

Number

ausgeben / formatieren

Zahlen können formatiert ausgegeben werden. Ist kein pattern angegeben, werden Ganzzahlen ohne und Kommazahlen mit Nachkommastellen ausgegeben. Der Tausender- und Dezimal-Trenner kann mittels gs bzw. ds spezifiziert werden. Der Rundungsmodus ist standardmäßig HALF_UP (normales kaufmännisches Runden) und kann via rounding geändert werden (siehe Java-Docs für mögliche Werte).

<bx:json.Zahl [pattern=""] [locale=""] [gs=""] [ds=""] [rounding=""] />

locale sollte immer angegeben werden, da sonst die Standardeinstellung vom Server genutzt wird, die sich aber nach Updates oder Umzügen ändern kann.

vergleichen

Folgende Vergleiche sind möglich:

Parameter ausgeführter Vergleich
equals ==
gt >
gte >=
lt <
lte <=
Das Ergebnis kann jeweils mit not umgekehrt werden. Für alle Vergleiche ist auch eine verkürzte Variante mit truefalse möglich (hier wird der jeweilige Wert von true oder false ausgegeben).
<bx:json.Zahl [equals=""] [gt=""] [gte=""] [lt=""] [lte=""] [not]>
</bx:json.Zahl>
 
<bx:json.Zahl [equals=""] ... [true=""] [false=""] />

Date

Ab Version 2.6.8

Da es im JSON keinen nativen Datumstyp gibt, wird mittels dem Tag-Parameter date der vorhande String-Wert im JSON als Datum interpretiert.

Eingabeformat

Ist lediglich date ohne weiteren Wert im Tag angegeben, werden einige Standard-Patterns unterstützt:

<bx:json.mydate date />

Es wird folgende Liste in dieser Reihenfolge durchprobiert:

  • dd.MM.yy HH:mm:ss
  • dd.MM.yy HH:mm
  • dd.MM.yy HH.mm
  • dd.MM.yy
  • yyyy-MM-dd'T'HH:mm:ssZ (ISO 8601) Alternativ kann ein eigenes Pattern angegeben werden:
<bx:json.mydate date="dd/MM/yyyy" />

Es muss nicht der gesamte String matchen. Möchte man z.B. aus dem Wert "20.08.2018 um 08:50" nur das Datum ziehen, genügt das Pattern dd.MM.yyyy.

Ausgabeformat

Standardmäßig wird das geparste Datum anhand des Patterns dd.MM.yyyy HH:mm:ss ausgegeben. Es kann auch ein eigenes Pattern angegeben werden:

<bx:json.mydate date pattern="dd.MM. HH:mm" />

Werden z.B. Tages- oder Monatsnamen ausgegeben, wird über locale die gewünschte Sprache eingestellt:

<bx:json.mydate date="yyyy-MM-dd" pattern="EEEE, d. MMMM" locale="de" />

Datum/Zeit modifizieren

Durch die Angabe von verschiedenen Parametern können alle Teile des Ausgangsdatums angepasst werden. Es kann eine Zahl angegeben werden, um den entsprechenden Wert absolut zu setzen. Dabei kann dieser Zahl ein Plus (+) oder Minus (-) vorangestellt werden, um die Modifikation stattdessen relativ durchzuführen. Die möglichen Parameter sind (werden auch in dieser Reihenfolge angewandt):

  • year
  • month (im Bereich 1 - 12)
  • day
  • weekday (s.u.)
  • hour
  • minute
  • second Etwaige Überschreitungen (z.B. +15 Monate) werden entsprechend weiter gezählt (also +1 Jahr und 3 Monate).

Beispiel (Tag auf den 15. Februar setzen, zwei Stunden dazu zählen, zehn Jahre abziehen):

<bx:json.mydate date day="15" month="2" hour="+2" year="-10" />

Mögliche Werte beim Wochentag sind dabei folgende Werte (keine Zahlen erlaubt):

  • mo / di / mi / do / fr / sa / so
  • mon / tue / wed / thu / fri / sat / sun Es wird auf den entsprechenden Wochentag in der gleichen Woche gewechselt. Auch hier gibt es die Möglichkeit ein Plus oder Minus voranzustellen, dann wird entsprechend auf den Tag in der nächsten bzw. vorherigen Woche gewechselt.

Trifft der angegebene Wochentag bereits auf das Ausgangsdatum zu, wird das Datum nicht geändert. Dieses Verhalten kann durch Angabe eines doppelten Plus (++) oder Minus (--) überschrieben werden (es wird dann auch gewechselt, falls der Wochentag schon zutrifft).

Zeitliche Abfrage

Eine weitere Funktion ist die Abfrage, ob das Ausgangsdatum (bzw. das modifizierte Datum) vor oder nach dem jetzigen Zeitpunkt (Zeit auf dem Server) bzw. heute liegt. Der Unterschied ist, dass beim Vergleich mit heute die Uhrzeit nicht beachtet wird, sondern nur der Datumsteil.

Die folgenden Abfragen sind möglich:

  • before-now
  • after-now
  • before-today
  • after-today
  • today (ist am heutigen Tag) Das Tag kann dabei entweder offen (optional noch mit not) oder geschlossen mit den true und/oder false Parametern genutzt werden. Beispiele:
<bx:json.mydate date before-now>in der Vergangenheit</bx:json.mydate>
<bx:json.mydate date after-now not>auch in der Vergangenheit oder genau jetzt</bx:json.mydate>
 
<bx:json.mydate date before-today true="vor heute" />
<bx:json.mydate date after-today false="vor heute oder heute" />
 
<bx:json.mydate date today true="am heutigen Tag" false="nicht heute" />

String

ausgeben

Im einfachsten Fall wird der String ausgegeben und ggf. automatisch encoded (siehe Encodings für andere Encoding-Möglichkeiten).

<bx:json.Text />

vergleichen

Strings können auch verglichen werden. Die Angabe von ignoreCase bewirkt, dass Groß-/Kleinschreibung nicht beachtet wird, not kehrt das Ergebnis um.

Parameter Vergleich
matches Vergleich via RegEx (regulärem Ausdruck), String muss diesem komplett entsprechen
equals Gleichheit
contains der String muss den Wert das Parameters beinhalten
Auch hier ist eine verkürzte Variante mit true und false möglich.
<bx:json.Text [matches=""] [equals=""] [contains=""] [ignoreCase] [not]>
</bx:json.Text>
 
<bx:json.Text [matches=""] [equals=""] [contains=""] [ignoreCase] [true=""] [false=""] />

BatixRecord

Steht in den JSON-Daten im Feld eine Batix-ID, so kann hier auch ein Container angebunden werden. Das Tag verhält sich dann wie bx:record, es können innerhalb die normalen Container-Tags wie bx:recordfield oder bx:recorddata benutzt werden. Auch der Zugriff von außerhalb des Tags mit z.B. bx:recorddata.nav ist möglich.

Der Datensatz wird anhand des Feldes ID rausgesucht. Es kann aber auch via linkfield auf ein anderes Feld im Datensatz verwiesen werden (Text oder Einzelverknüpfung), hierbei wird der erste gefundene Datensatz genommen.

Wird kein Datensatz mit entsprechender ID (oder Wert im Feld) gefunden, wird nichts ausgegeben. Die Angabe von dummy bewirkt, dass stattdessen ein leerer Datensatz vorgehalten wird (der Tag-Inhalt wird ausgeführt, innere bx:recordfield-Tags etc. geben aber nichts aus, wie bei bx:record).

<bx:json.IDFeld pool="Container" [linkfield=""] [dummy]>
  <bx:recordfield... />
  <bx:recorddata.if... />
</bx:json.IDFeld >
 
<bx:recorddata.nav object="IDFeld">
  <bx:recorddata.total />
</bx:recorddata.nav>

Array

Ein Array funktioniert analog zu z.B. bx:containerfilter, für jedes Element wird der Tag-Inhalt einmal ausgeführt. Das Start-Element kann dabei mittels index (0-basiert) und die maximale Anzahl Durchläufe via max geregelt werden.

Um das aktuelle Element im Durchlauf auszugeben wird einfach <bx:json /> verwendet. Je nach Datentyp (in Arrays können auch verschiedene Datentypen vorhanden sein) können hier die entsprechenden Funktionen verwendet werden (z.B. formatierte Ausgabe bei Zahlen).

<bx:json.Liste [index="20"] [max="10"]>
  <bx:json />
</bx:json.Liste>

Es können die meißten Funktionen von bx:recorddata verwendet werden:

<bx:json.Liste>
  ...
</bx:json.Liste>
<bx:recorddata.nav object="Liste">
  Gesamt: <bx:recorddata.total/><br>
  <bx:recorddata.navlist max="5">
    ...

Ferner gibt es noch folgende Hilfsmethoden:

<bx:json.Liste>
  <bx:json {first|last} [not]>...</bx:json> <!-- Inhalt nur ausführen, wenn es das erste oder letzte Element ist -->
  <bx:json {first|last} [true=...] [false=...] /> <!-- dito, nur verkürzte true/false Schreibweise -->
  <bx:json index [add="5"] /> <!-- aktuellen Durchlauf-Index ausgeben, ggf. etwas dazu addieren -->
  <bx:json total /> <!-- Gesamtanzahl der Elemente ausgeben -->
  <bx:json empty [not]>...</bx:json> <!-- ausführen, falls das Array leer ist -->
  <bx:json empty [true=...] [false=...] /> <!-- dito, nur mit true/false -->
  <bx:json cols=...> <!-- funktioniert wie bx:recorddata.cols -->
</bx:json.Liste>

Für mehr Informationen zu cols siehe die entsprechende Passage bei bx:recorddata.

Datensätze

Befinden sich im Array Strings, welche Batix-IDs sind, kann mittels pool eine Schleife über Datensätze (wie z.B. bx:containerfilter) aufgemacht werden. Falls die ID in einem anderen Feld steht, kann dies wieder via linkfield referenziert werden. Innerhalb des Json-Tags sind alle normalen Datensatz-Tags verfügbar. dummy verhält sich im Falle eines leeren Arrays wie ein leerer Datensatz (ansonsten wird der Inhalt nicht ausgeführt).

Falls im Array (zusätzlich) nicht-Strings sind, wird im Log eine Warnung ausgegeben.

<bx:json.Kunden pool="Shop_Kunden" [linkfield=""] [dummy]>
  <bx:recordfield... />
  <bx:recorddata.if... />
</bx:json.Kunden>

Object

Um in ein JSON-Objekt tiefer einzusteigen, wird das Json-Tag (analog zu bx:recordfield bei Verknüpfungen) geschachtelt.

<bx:json data="...">
  <bx:json.Kunde>
    <bx:json.Name />
  </bx:json.Kunde>
</bx:json>

Objekt-Einträge durchgehen

ab Version 2.6.9

Ähnlich einem Array können die Elemente eines Objekts (jeweils Key und Value) durchgegangen werden. Das ist hilfreich, falls man die Struktur der Map (des Objektes) nicht direkt kennt (vor allem wenn die Keys dynamisch sind). Dies erfolgt durch Angabe von each, innerhalb dieser Schleife können dann <bx:json key> und <bx:json value> benutzt werden um auf den entsprechenden Bestandteil zuzugreifen.

Das Tag mit key hat dabei immer einen String im Hintergrund, wobei das Tag mit value den Typ des Unterelementes hat (entsprechend sind dort auch wieder die verschiedenen Operationen je Typ möglich).

<bx:json data="...">
  <bx:json.Kunde each>
    Feld '<bx:json key />' hat den Wert '<bx:json value />'
  </bx:json.Kunde>
</bx:json>

JsonPath

Mittels JsonPath-Expressions können relativ einfach verschachtelte Strukturen angesprochen werden. Das erspart lästiges Verschachteln von Json-Tags. Auf der GitHub Seite gibt es mehr Details zur Syntax, hier ist nur das Nötigste erwähnt.

Operator Bedeutung
$ aktuelle bx:json Node, der Startpunkt damit beginnen alle Expressions
@ aktuelle Node in der Expression in Filtern benutzt
* Wildcard für aktuelles Level
.. Wildcard für beliebige Sub-Level
.Name oder ['Name'] Feld Name
.Adresse.Ort Feld Ort im Unterobjekt Adresse
[x] / [x:y] Index bzw. Range im Array
[?(...)] Filter
Es sind auch Statistikfunktionen verfügbar (siehe Link, z.B. min()), diese sind aber nur auf reine Arrays aus Zahlen anwendbar. Mittels Filtern können Vergleiche angestellt und dabei sogar andere Felder referenziert werden. (z.B. Ist die Zahl in Feld X größer als die Zahl in Feld Y?)

Sobald etwas passendes gefunden wurde, können die Typ-spezifischen Funktionen verwendet werden. Wird nichts passendes zum Pfad gefunden, wird der Tag-Inhalt nicht ausgeführt (außer bei dummy).

Zu bemerken ist auch, dass path an beliebiger Stelle innerhalb verschachtelter Json-Tags verwendet werden kann, $ in der Pfadangabe entspricht dann dem direkten Parent-Objekt.

Beispiele:

<bx:json data="...">
  <bx:json path="$.Adresse.Name" equals="" false="nicht leer" />
  <!-- entspricht -->
  <bx:json path="$['Adresse']['Name']" equals="" false="nicht leer" />
  <!-- entspricht -->
  <bx:json.Adresse><bx:json.Name equals="" false="nicht leer" /></bx:json.Adresse>
  <!-- entspricht -->
  <bx:json.Adresse><bx:json path="$.Name" equals="" false="nicht leer" /></bx:json.Adresse>
 
  <!-- gibt das 2. Element im Array orders aus, je nach Datentyp der Elemente im Array können hier die spezifischen Typ-Funktionen genutzt werden -->
  <bx:json path="$.orders[1]" />
 
  <!-- gibt den höchsten Preis aus -->
  <bx:json path="$.prices.max()" pattern="0.00" locale="de" />
 
  <!-- gibt die Autoren aller Bücher aus -->
  <bx:json path="$.books[*].author">
    Autor: <bx:Json />
  </bx:json>
 
  <!-- dito, aber nur für Bücher mit einem Rating > 2 -->
  <!-- bei komplexen Expression empfiehlt sich ein Clipboard, gerade bei Sonderzeichen -->
  <bx:clipboard.cut name="expr">$.books[?(@.rating > 2)].author</bx:clipboard.cut>
  <bx:json path="clipboard:expr">
    Autor: <bx:Json />
  </bx:json>
 
  <!-- gibt die Preise aller Unterelemente (beliebige Tiefe) aus -->
  <bx:json path="$.store..price">
    Preis: <bx:Json locale="de" />
  </bx:json>
 
  <!-- gibt alle Preise aus, die kleiner als der Maximalpreis (ein Feld im JSON) sind -->
  <bx:clipboard.cut name="expr">$.store.articles[?(@.price < $.maxprice)]</bx:clipboard.cut>
  <bx:json path="clipboard:expr">
    Preis: <bx:Json locale="de" />
  </bx:json>
</bx:json>