Streaming JSON/de: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | [[JSON]] (JavaScript Object Notation) ist ein textbasiertes, standardisiertes Datenformat. Wie der Name sagt, sind JSON-Dokumente gültiger JavaScript-Code und können dort direkt in Objekte umgesetzt werden. An und für sich kann es aber zum Datenaustausch unabhängig von der verwendeten Programmiersprache verwendet werden. | |
| 2 | 2 | ||
| 3 | 3 | In diesem Tutorial wird erklärt, wie man JSON-Daten in ein Free-Pascal-Programm lädt um sie dort zu verarbeiten; Ebenso wird erklärt, wie man die Daten aus dem Programm in JSON konvertiert (um sie dann zum Beispiel an einen Webbrowser zu senden). | |
| 4 | 4 | ||
| 5 | 5 | ## Allgemeines und Voraussetzungen | |
| 6 | 6 | ||
| 7 | 7 | Das Laden und Speichern von Objekten erfolgt mit der Unit [[fpjsonrtti]]. Weiterhin ist die Unit Classes sinnvoll (mehr dazu weiter unten im Abschnitt Datenstruktur ?; Die [[Uses/de|uses]]-Anweisung sollte also mindestens diese beiden Units enthalten: | |
| 8 | 8 | ||
| 9 | 9 | ``` pascal | |
| 10 | 10 | uses Classes, fpjsonrtti; | |
| 11 | 11 | ``` | |
| 12 | 12 | ||
| 13 | 13 | Zum derzeitigen Zeitpunkt (Mai 2014) gibt es einige Unterschiede zum [[Streaming components/de|Streaming-Systems]] von Free Pascal: | |
| 14 | 14 | ||
| 15 | 15 | - JSON-Daten sind abhängig von Groß- und Kleinschreibung (case insensitive).1 Daraus folgt, dass die Eigenschaften der Free-Pascal-Objekte genau wie die JSON-Eigenschaften geschrieben werden müssen. | |
| 16 | 16 | - Es können keine Eigenschaften mit DefineProperties definiert werden; es können keine Referenzen auf Methoden (Event-Handler) gespeichert werden.2 | |
| 17 | 17 | - [[TCollection]] und [[TStrings]] können als Ersatz für Arrays verwendet werden. | |
| 18 | 18 | ||
| 19 | 19 | Mit den Quelltexten des Free-Pascal-Compilers wird im Verzeichnis `<fpc-quellen>/packages/fcl-json/examples/demortti.pp` ein Demoprogramm mitgeliefert. | |
| 20 | 20 | ||
| 21 | 21 | Als Beispiel wird im Folgenden immer diese JSON-Struktur verwendet. | |
| 22 | 22 | ||
| 23 | 23 | ``` JavaScript | |
| 24 | 24 | { | |
| 25 | 25 | "id" : 123, // ein Integer | |
| 26 | 26 | "obj" : { "name": "Hallo Welt!" }, // ein Objekt | |
| 27 | 27 | "coll" : [ { "name": "Objekt 1" }, { "name": "Object 2" } ], // zwei Objekte in einer TCollection | |
| 28 | 28 | "strings": [ "Hallo 1", "Hallo 2" ] // eine String-Liste | |
| 29 | 29 | } | |
| 30 | 30 | ``` | |
| 31 | 31 | ||
| 32 | 32 | Im eigenen Programmquelltext kann sie so definiert werden: | |
| 33 | 33 | ||
| 34 | 34 | ``` pascal | |
| 35 | 35 | const JSON_TESTDATA = | |
| 36 | 36 | '{'+LineEnding+ | |
| 37 | 37 | ' "id": 123,'+LineEnding+ | |
| 38 | 38 | ' "obj": { "name": "Hallo Welt!" },'+LineEnding+ | |
| 39 | 39 | ' "coll": [ { "name": "Objekt 1" }, { "name": "Objekt 2" } ],'+LineEnding+ | |
| 40 | 40 | ' "strings": [ "Hallo 1", "Hallo 2" ]'+LineEnding+ | |
| 41 | 41 | '}'; | |
| 42 | 42 | ``` | |
| 43 | 43 | ||
| 44 | 44 | ## Datenstruktur | |
| 45 | 45 | ||
| 46 | - | ||
| 46 | + | Als Basisklasse für die Daten bietet sich [[TPersistent]] aus der Unit Classes an, da für sie und alle Unterklassen [[Runtime Type Information (RTTI)|Laufzeit-Typinformationen]] (RTTI) erstellt werden. Diese werden für das Streaming zwingend benötigt. Da sich *fpjsonrtti* nicht in das Streaming-System integriert, kann auch jede andere Klasse, die mit dem Compilerschalter `{$M+}` übersetzt wurde, verwendet werden. | |
| 47 | 47 | ||
| 48 | 48 | Alle zu lesenden Eigenschaften müssen als [[Property/de|property]] im [[Published/de|published]]-Abschnitt der Klasse deklariert werden. In der Regel, kann hier mit read und write direkt auf ein Datenfeld (die Variable) verweisen werden. Wenn man möchte, können natürlich auch Getter- und Setter-Methoden verwendet werden. | |
| 49 | 49 | ||
| 50 | 50 | Aus der JSON-Struktur ergibt sich folgende Klassendefinition. | |
| 51 | 51 | ||
| 52 | 52 | ``` pascal | |
| 53 | 53 | type | |
| 54 | 54 | TNameObject = class(TCollectionItem) // Klasse für die Eigenschaft 'obj' und die TCollection | |
| 55 | 55 | private | |
| 56 | 56 | fName: String; | |
| 57 | 57 | published | |
| 58 | 58 | property name: String read fName write fName; | |
| 59 | 59 | end; | |
| 60 | 60 | ||
| 61 | 61 | TBaseObject = class(TPersistent) // Klasse für die gesamte JSON-Struktur | |
| 62 | 62 | private | |
| 63 | 63 | fid: Integer; | |
| 64 | 64 | fObj: TNameObject; | |
| 65 | 65 | fColl: TCollection; | |
| 66 | 66 | fStrings: TStrings; | |
| 67 | 67 | public | |
| 68 | 68 | constructor Create; | |
| 69 | 69 | destructor Destroy; override; | |
| 70 | 70 | published // alle Eigenschaften müssen published sein | |
| 71 | 71 | property id: Integer read fid write fid; | |
| 72 | 72 | property obj: TNameObject read fObj write fObj; | |
| 73 | 73 | property coll: TCollection read fColl; | |
| 74 | 74 | property strings: TStrings read fStrings; | |
| 75 | 75 | end; | |
| 76 | 76 | ``` | |
| 77 | 77 | ||
| 78 | 78 | Die Klasse `TNameObject` wurde von [[TCollectionItem]] abgeleitet. Damit kann sie sowohl für die Eigenschaft *obj* als auch in der Collection verwendet werden. Wenn dies nicht gewünscht ist, müssen hier zwei unterschiedliche Klassen definiert werden. | |
| 79 | 79 | ||
| 80 | 80 | Im Konstruktor der Klasse TBaseObject müssen die TCollection sowie die String-Liste erstellt und im Destruktor wieder freigegeben werden. | |
| 81 | 81 | ||
| 82 | 82 | ``` pascal | |
| 83 | 83 | constructor TBaseObject.Create; | |
| 84 | 84 | begin | |
| 85 | 85 | // Collection und StringList erstellen | |
| 86 | 86 | fColl := TCollection.Create(TNameObject); | |
| 87 | 87 | fStrings := TStringList.Create; | |
| 88 | 88 | fObj := TNameObject.Create(nil); | |
| 89 | 89 | end; | |
| 90 | 90 | ||
| 91 | 91 | destructor TBaseObject.Destroy; | |
| 92 | 92 | begin | |
| 93 | 93 | // Collection und StringList wieder freigeben | |
| 94 | 94 | fColl.Free; | |
| 95 | 95 | fStrings.Free; | |
| 96 | 96 | fObj.Free; | |
| 97 | 97 | inherited Destroy; | |
| 98 | 98 | end; | |
| 99 | 99 | ``` | |
| 100 | 100 | ||
| 101 | 101 | Sofern Sie keine weitere Funktionalität in den Datenklassen haben möchten, ist ihre Definition bereits fertig. | |
| 102 | 102 | ||
| 103 | 103 | ## JSON laden | |
| 104 | 104 | ||
| 105 | 105 | Mit der Methode `Procedure JSONToObject(Const JSON : TJSONStringType; AObject : TObject);` der Klasse [[TJSONDeStreamer]] können Sie JSON-Daten direkt an ein *vorhandenes* Objekt zuweisen. Bevor Sie die Methode aufrufen, müssen Sie TJSONDeStreamer und das Ziel-Objekt erstellen. | |
| 106 | 106 | ||
| 107 | 107 | Die folgende Methode lädt die Daten aus der JSON-Struktur `JSON_TESTDATA` in das Objekt o und gibt die aktuellen Werte der Eigenschaften danach auf der Konsole aus. | |
| 108 | 108 | ||
| 109 | 109 | ``` pascal | |
| 110 | 110 | procedure DeStreamTest; | |
| 111 | 111 | var | |
| 112 | 112 | DeStreamer: TJSONDeStreamer; | |
| 113 | 113 | o: TBaseObject; | |
| 114 | 114 | no: TNameObject; | |
| 115 | 115 | s: String; | |
| 116 | 116 | begin | |
| 117 | 117 | WriteLn('DeStream test'); | |
| 118 | 118 | WriteLn('======================================'); | |
| 119 | 119 | ||
| 120 | 120 | // DeStreamer-Objekt und Ziel-Objekt erstellen | |
| 121 | 121 | DeStreamer := TJSONDeStreamer.Create(nil); | |
| 122 | 122 | o := TBaseObject.Create; | |
| 123 | 123 | try | |
| 124 | 124 | // JSON-Daten in das Objekt o laden | |
| 125 | 125 | DeStreamer.JSONToObject(JSON_TESTDATA, o); | |
| 126 | 126 | // ID ausgeben | |
| 127 | 127 | WriteLn(o.id); | |
| 128 | 128 | // Objekt-Name ausgeben | |
| 129 | 129 | WriteLn(o.obj.name); | |
| 130 | 130 | // alle Namen der Objekte ausgeben | |
| 131 | 131 | for TCollectionItem(no) in o.coll do | |
| 132 | 132 | Writeln(no.name); | |
| 133 | 133 | // alle Strings ausgeben | |
| 134 | 134 | for s in o.strings do | |
| 135 | 135 | WriteLn(s); | |
| 136 | 136 | ||
| 137 | 137 | // aufräumen | |
| 138 | 138 | finally | |
| 139 | 139 | o.Destroy; | |
| 140 | 140 | DeStreamer.Destroy; | |
| 141 | 141 | end; | |
| 142 | 142 | end; | |
| 143 | 143 | ``` | |
| 144 | 144 | ||
| 145 | 145 | ## JSON speichern | |
| 146 | 146 | ||
| 147 | 147 | Um ein Objekt in einen JSON-Text zu überführen, wird die Klasse [[TJSONStreamer]] verwendet. Hier wird die Methode `Function ObjectToJSONString(AObject : TObject) : TJSONStringType;` verwendet. | |
| 148 | 148 | ||
| 149 | 149 | In der folgenden Prozedur wird ein Objekt erstellt, mit den Testdaten befüllt und anschließend nach JSON überführt. Der JSON-Text wird auf der Konsole ausgegeben. Es kann nicht festgelegt werden, in welcher Reihenfolge die Eigenschaften ausgeben werden | |
| 150 | 150 | ||
| 151 | 151 | ``` pascal | |
| 152 | 152 | procedure StreamTest; | |
| 153 | 153 | var | |
| 154 | 154 | Streamer: TJSONStreamer; | |
| 155 | 155 | o: TBaseObject; | |
| 156 | 156 | JSONString: String; | |
| 157 | 157 | begin | |
| 158 | 158 | WriteLn('Stream test'); | |
| 159 | 159 | WriteLn('======================================'); | |
| 160 | 160 | ||
| 161 | 161 | Streamer := TJSONStreamer.Create(nil); | |
| 162 | 162 | o := TBaseObject.Create; | |
| 163 | 163 | try | |
| 164 | 164 | // Daten festlegen | |
| 165 | 165 | o.id := 123; | |
| 166 | 166 | o.obj.name := 'Hallo Welt!'; | |
| 167 | 167 | TNameObject(o.coll.Add).name := 'Objekt 1'; | |
| 168 | 168 | TNameObject(o.coll.Add).name := 'Objekt 2'; | |
| 169 | 169 | o.strings.Add('Hallo 1'); | |
| 170 | 170 | o.strings.Add('Hallo 2'); | |
| 171 | 171 | ||
| 172 | 172 | Streamer.Options := Streamer.Options + [jsoTStringsAsArray]; // Strings als JSON-Array ausgeben | |
| 173 | 173 | // nach JSON überführen und ausgeben | |
| 174 | 174 | JSONString := Streamer.ObjectToJSONString(o); | |
| 175 | 175 | WriteLn(JSONString); | |
| 176 | 176 | ||
| 177 | 177 | // aufräumen | |
| 178 | 178 | finally | |
| 179 | 179 | o.Destroy; | |
| 180 | 180 | Streamer.Destroy; | |
| 181 | 181 | end; | |
| 182 | 182 | end; | |
| 183 | 183 | ``` | |
| 184 | 184 | ||
| 185 | 185 | ## Ausblick | |
| 186 | 186 | ||
| 187 | 187 | Mit dem vorgestellten Wissen können einfache und komplexe JSON-Datenstrukturen in Free Pascal geladen werden. Sollte eine Vor- oder Nachbearbeitung der JSON-Daten notwendig sein, können die Textdaten zunächst mit der Klasse [[TJSONParser]] aus der Unit [[jsonparser]] in eine JSON-Datenstruktur geladen werden und dann mit der Unit [[fpJSON]] beliebig manipuliert werden. | |
| 188 | 188 | ||
| 189 | 189 | Über die Eigenschaft Options der Klasse TJSONStreamer kann gesteuert werden, wie die Ausgabe die eigenen Datenstrukturen in JSON abgebildet werden. | |
| 190 | 190 | ||
| 191 | 191 | ## Siehe auch | |
| 192 | 192 | ||
| 193 | 193 | - [[JSON]] | |
| 194 | 194 | - [[fcl-json]] | |
| 195 | 195 | - [[Streaming components/de]] | |
| 196 | 196 | ||
| 197 | 197 | ## Einzelnachweise | |
| 198 | 198 | ||
| 199 | - | ||
| 200 | - | ||
| 201 | - | ||
| 202 | - | ||
| 203 | - | ||
| 199 | + | 1. <http://lists.freepascal.org/fpc-pascal/2013-January/036254.html> | |
| 204 | 200 | ||
| 205 | 201 | 2. \<div id="ref2\>http://lists.lazarus.freepascal.org/pipermail/lazarus/2011-January/058878.html | |
| 206 | - | ||
| 207 | 202 | ||
| 208 | 203 | --- | |
| 209 | 204 | *From the Free Pascal wiki, page "Streaming JSON/de", as it stood on 2022-08-06 after a change by Kai Burghardt, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
