Einleitung
Bei der Arbeit mit APIs ist es fast unvermeidbar, JSON-Daten verarbeiten zu müssen. FileMaker stellt uns zwar praktische Funktionen wie JSONGetElement, JSONListKeys, JSONListValues oder JSONGetElementType zur Verfügung, aber spätestens, wenn die Struktur verschachtelt wird, kann es aufwendig werden, alle Werte einzeln herauszuholen. Da kam mir die Idee, eine universell einsetzbare Funktion zu schreiben, die alles rekursiv erledigt:
•Alle Werte (inkl. verschachtelter Objekte und Arrays) sollen gelesen werden.
•Jeder Wert soll in einer globalen Variable (z. B. $$data_user_id) landen.
•Zusätzlich wollte ich am Ende eine Liste all dieser Variablen erhalten, damit ich programmatisch weiß, welche Variablen existieren – und sie bei Bedarf auch wieder löschen kann.
Erste Versuche: Manuelle JSON-Auswertung
Zunächst habe ich versucht, „ganz normal“ in einem Skript durch das JSON zu laufen:
1.Mit JSONListKeys( json ; «» ) alle Top-Level-Keys ermitteln.
2.Pro Key herausfinden, ob dahinter ein Wert oder ein verschachteltes Objekt oder Array steht.
3.Falls verschachtelt, müsste ich wieder das Gleiche machen, diesmal aber mit dem Key-Pfad (z. B. data, dann data.user usw.).
Das Skript sah schnell unübersichtlich aus: viele Schleifen, viele IF-Bedingungen. Außerdem war die Pflege mühsam. Daher dachte ich an eine Custom Function.
Idee einer rekursiven Custom Function
FileMaker hat zwei Möglichkeiten, Schleifen in Custom Functions umzusetzen: Entweder über rekursive Aufrufe (also sich selbst aufrufen) oder über die Funktion While().
•Mit Rekursion kann man schön verschachtelte Strukturen abarbeiten, weil man immer wieder dieselbe Funktion für jeden nächsten „Level“ aufruft.
•Seit FileMaker 19 gibt es die While()-Funktion, die sich gut für Iterationen eignet (z. B. über alle Keys).
Also wollte ich beide Techniken kombinieren:
1.Eine While()-Schleife, um alle Keys einer aktuellen Ebene zu durchlaufen.
2.Ein rekursiver Aufruf, wenn ich auf ein verschachteltes Objekt oder Array stoße.
Zwischenstand: Werte in globale Variablen
Erster Teilschritt: nur die globale Variablensetzung.
•Jedes Mal, wenn ich einen „einfachen“ Wert (String, Number, Boolean, …) vor mir habe, will ich eine globale Variable anlegen:
•Der Name der Variable soll aus dem JSON-Pfad abgeleitet sein, wobei ich «.» durch «_» ersetze.
• Beispiel: data.user.name wird zu $$data_user_name.
Für diesen Schritt nutze ich in der Custom Function so etwas wie:
Evaluate (
«Let ( [» &
«$$» & Substitute ( _fullPath ; «.» ; «_» ) & » = » & Quote ( _value ) &
«] ; 1 )»
)
Dabei sorgt Evaluate() dafür, dass die Textdarstellung in eine echte FileMaker-Anweisung umgewandelt wird, die tatsächlich die globale Variable setzt.
Rekursion
Bei verschachtelten Objekten oder Arrays muss die Funktion sich selbst aufrufen, mit dem neuen Pfad:
Case(
_type = «object» or _type = «array» ;
SetVarsFromJSON( json ; _fullPath );
// sonst: Terminal-Wert in Variable
…
)
So hangelt sich die Funktion Ebene für Ebene weiter.
Zusatzwunsch: Liste aller angelegten Variablen
Nach den ersten Tests merkte ich: Ich wollte neben dem Setzen der Variablen auch wissen, welche Variablen eigentlich angelegt wurden. Das macht es einfacher, nachher alle Variablen zu löschen oder sie zu dokumentieren.
Daher sollte die Funktion ein Ergebnis zurückgeben: und zwar eine Return-getrennte Liste aller Variablennamen – ohne Werte, nur die Namen (z. B. $$data_user_name).
Also kam ich auf folgende Strategie:
•Innerhalb der Schleife sammel ich jeden neu angelegten Variablennamen (bzw. alle Variablennamen, falls die Funktion rekursiv aufgerufen wurde).
•Das Ganze „akkumuliert“ sich dann in einer Variable, die ich in FileMaker gerne _acc nenne (Kurzform für „Accumulator“).
•Zum Schluss gebe ich diesen Akkumulator als Return-Wert zurück.
Der Knackpunkt:
•Wenn ich auf ein Objekt oder Array stoße und rekursiv SetVarsFromJSON(…) aufrufe, erhalte ich eine Liste der Variablen, die in diesem „Tieflauf“ gesetzt wurden.
•Diese Liste muss ich an meinen eigenen _acc anhängen (z. B. über List() oder einen simplen Zeilenumbruch).
Finale Formel
Am Ende entstand die folgende, recht kompakte Custom Function (hier noch einmal in Gänze):
/*
SetVarsFromJSON ( json ; pfad )
——————————–
Diese Funktion durchläuft rekursiv das JSON an Pfad «pfad» und:
1) Schreibt jedes „Terminal-Feld“ (Zahl, String, Bool usw.) in
eine globale Variable $$…
2) Ruft sich bei Objekten und Arrays rekursiv auf
3) Liefert am Ende eine Liste ALLER gesetzten Variablennamen zurück
(eine Zeile pro Variablename).
Anwendung:
// 1) Als Custom Function definieren
// 2) In einem Skript aufrufen, z. B.:
Set Variable [ $varList ; Value: SetVarsFromJSON( $APIResponse ; «» ) ]
Dann steht in $varList eine Liste aller erzeugten Variablen:
$$id
$$user_name
$$user_friends_0_id
…
*/
Let (
[
// Alle Keys auf der aktuellen Pfadebene
~keys = JSONListKeys ( json ; pfad ) ;
~count = ValueCount ( _keys )
];
// Wir verwenden eine While-Schleife (FileMaker ≥ 19),
// um über alle Keys zu iterieren.
While (
// 1) Startwerte für die Schleife
[
~i = 0 ; // Schleifenindex
~acc = «» // Akkumulator für Variablennamen-Liste
] ;
// 2) Abbruchbedingung
~i < ~count ;
// 3) Schleifenrumpf (pro Durchlauf)
[
// a) Aktuellen Key und Pfad bestimmen
~key = GetValue ( ~keys ; ~i + 1 ) ;
~fullPath = If ( IsEmpty ( pfad ) ; ~key ; pfad & «.» & ~key ) ;
// b) Variablenname mit Unterstrichen statt Punkten
~varName = «$$» & Substitute ( ~fullPath ; «.» ; «_» ) ;
// c) Typ und Wert aus JSON auslesen
~type = JSONGetElementType ( json ; ~fullPath ) ;
~value = JSONGetElement ( json ; ~fullPath ) ;
// d) Unterscheiden zwischen verschachtelt (object/array) und Terminal (string/number/…)
~sub = Case (
// Objekt oder Array -> rekursiver Aufruf
~type = «object» or ~type = «array» ;
SetVarsFromJSON ( json ; ~fullPath ) ;
// Andernfalls -> globale Variable setzen; kein rekursives Durchlaufen nötig
Evaluate (
«Let ( [ » &
~varName & » = » & Quote ( ~value ) &
» ] ; 1 )»
)
) ;
// e) Wenn rekursiv, liefert _sub bereits eine Liste von Variablennamen.
// Bei einem Terminal-Wert (also sonst) ist _sub leer, dafür wollen wir
// den eigenen Variablennamen hinzufügen.
~newVars = Case (
~type = «object» or _type = «array» ;
// _sub enthält Liste aller Variablen aus der Tiefe
List ( ~acc ; ~sub ) ;
// Terminal -> füre eigenen Variablennamen in die Liste ein
List ( ~acc ; ~varName )
) ;
// f) Akkumulator aktualisieren
~acc = ~newVars ;
// g) Schleifenindex erhöhen
~i = ~i + 1
] ;
// 4) Rückgabewert der While-Funktion (nach Schleifenende)
~acc
)
)
Wie man sie nutzt
1.Custom Function anlegen:
Im File → Manage → Custom Functions-Dialog in FileMaker die Funktion SetVarsFromJSON(json; pfad) neu anlegen und den obigen Code eingeben.
2.Aufruf im Skript:
Set Variable [ $varList ; Value: SetVarsFromJSON( $APIResponse ; «» ) ]
• $APIResponse ist der JSON-Text, den man beispielsweise von einer Web-API bekommen hat.
•Im Ergebnis landen alle Werte in $$…-Variablen.
•$varList enthält eine Return-getrennte Liste all dieser Variablennamen, wie:
$$user_id
$$user_name
$$items_0_id
$$items_0_title
$$items_1_id
…
3.Löschen der Variablen:
Man kann nun ein einfaches Skript bauen, das die Einträge in $varList abläuft und jede Variable wieder leert, zum Beispiel:
Set Variable [ $i ; Value: 1 ]
Loop
Exit Loop If [ $i > ValueCount( $varList ) ]
Set Variable [ GetValue($varList ; $i) ; Value: «» ]
Set Variable [ $i ; Value: $i + 1 ]
End Loop
Fazit
Die obige Custom Function entstand in kleinen Schritten:
•Zuerst ein kleines Skript, das Top-Level-Keys in globale Variablen schreiben kann.
•Dann die Idee, Objekte/Arrays rekursiv zu durchlaufen.
•Schließlich der Wunsch, alle erzeugten Variablennamen zurückgeliefert zu bekommen, um sie programmatisch zu verwalten.
Mit der finalen Version gelingt es, jegliche JSON-Struktur – egal wie tief verschachtelt – vollautomatisch in globale Variablen zu schreiben und diese Variablennamen in einer Liste bereitzustellen. Wer regelmäßig JSON-Werte aus APIs verarbeitet und dynamisch in FileMaker-Variablen laden möchte, für den ist dies eine enorme Erleichterung und Zeitersparnis.