Streaming JSON/de

From Free Pascal wiki
Jump to: navigation, search

Deutsch (de) English (en) 日本語 (ja) русский (ru)

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.

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).

Allgemeines und Voraussetzungen

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-Anweisung sollte also mindestens diese beiden Units enthalten:

uses Classes, fpjsonrtti;


Zum derzeitigen Zeitpunkt (Mai 2014) gibt es einige Unterschiede zum Streaming-Systems von Free Pascal:

  • 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.
  • Es können keine Eigenschaften mit DefineProperties definiert werden; es können keine Referenzen auf Methoden (Event-Handler) gespeichert werden.2
  • TCollection und TStrings können als Ersatz für Arrays verwendet werden.

Mit den Quelltexten des Free-Pascal-Compilers wird im Verzeichnis <fpc-quellen>/packages/fcl-json/examples/demortti.pp ein Demoprogramm mitgeliefert.

Als Beispiel wird im Folgenden immer diese JSON-Struktur verwendet.

{
  "id"     : 123,                                                // ein Integer
  "obj"    : { "name": "Hallo Welt!" },                          // ein Objekt
  "coll"   : [ { "name": "Objekt 1" }, { "name": "Object 2" } ], // zwei Objekte in einer TCollection
  "strings": [ "Hallo 1", "Hallo 2" ]                            // eine String-Liste
}

Im eigenen Programmquelltext kann sie so definiert werden:

const JSON_TESTDATA =
'{'+LineEnding+
'  "id": 123,'+LineEnding+
'  "obj": { "name": "Hallo Welt!" },'+LineEnding+
'  "coll": [ { "name": "Objekt 1" }, { "name": "Objekt 2" } ],'+LineEnding+
'  "strings": [ "Hallo 1", "Hallo 2" ]'+LineEnding+
'}';

Datenstruktur

Als Basisklasse für die Daten bietet sich TPersistent aus der Unit Classes an, da für sie und alle Unterklassen 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.

Alle zu lesenden Eigenschaften müssen als property im 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.

Aus der JSON-Struktur ergibt sich folgende Klassendefinition.

type
  TNameObject = class(TCollectionItem) // Klasse für die Eigenschaft 'obj' und die TCollection
  private
    fName: String;
  published
    property name: String read fName write fName;
  end;  
 
  TBaseObject = class(TPersistent)  // Klasse für die gesamte JSON-Struktur
  private
    fid: Integer;
    fObj: TNameObject;
    fColl: TCollection;
    fStrings: TStrings;
  public
    constructor Create;
    destructor Destroy; override;
  published  // alle Eigenschaften müssen published sein
    property id: Integer read fid write fid;
    property obj: TNameObject read fObj write fObj;
    property coll: TCollection read fColl;
    property strings: TStrings read fStrings;
  end;

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.

Im Konstruktor der Klasse TBaseObject müssen die TCollection sowie die String-Liste erstellt und im Destruktor wieder freigegeben werden.

constructor TBaseObject.Create;
begin
  // Collection und StringList erstellen
  fColl    := TCollection.Create(TNameObject);
  fStrings := TStringList.Create;
  fObj     := TNameObject.Create(nil);
end;
 
destructor TBaseObject.Destroy;
begin
  // Collection und StringList wieder freigeben
  fColl.Free;
  fStrings.Free;
  fObj.Free;
  inherited Destroy;
end;

Sofern Sie keine weitere Funktionalität in den Datenklassen haben möchten, ist ihre Definition bereits fertig.

JSON laden

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.

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.

procedure DeStreamTest;
var
  DeStreamer: TJSONDeStreamer;
  o: TBaseObject;
  no: TNameObject;
  s: String;
begin
  WriteLn('DeStream test');
  WriteLn('======================================');
 
  // DeStreamer-Objekt und Ziel-Objekt erstellen
  DeStreamer := TJSONDeStreamer.Create(nil);
  o := TBaseObject.Create;
  try
    // JSON-Daten in das Objekt o laden
    DeStreamer.JSONToObject(JSON_TESTDATA, o);
    // ID ausgeben
    WriteLn(o.id);
    // Objekt-Name ausgeben
    WriteLn(o.obj.name); 
    // alle Namen der Objekte ausgeben
    for TCollectionItem(no) in o.coll do
      Writeln(no.name);
    // alle Strings ausgeben
    for s in o.strings do
      WriteLn(s);
 
  // aufräumen
  finally
    o.Destroy;
    DeStreamer.Destroy;
  end;
end;

JSON speichern

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.

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

procedure StreamTest;
var
  Streamer: TJSONStreamer;
  o: TBaseObject;
  JSONString: String;
begin
  WriteLn('Stream test');
  WriteLn('======================================');
 
  Streamer := TJSONStreamer.Create(nil);
  o := TBaseObject.Create;
  try
    // Daten festlegen
    o.id := 123;
    o.obj.name := 'Hallo Welt!';
    TNameObject(o.coll.Add).name := 'Objekt 1';
    TNameObject(o.coll.Add).name := 'Objekt 2';
    o.strings.Add('Hallo 1');
    o.strings.Add('Hallo 2');
 
    Streamer.Options := Streamer.Options + [jsoTStringsAsArray]; // Strings als JSON-Array ausgeben
    // nach JSON überführen und ausgeben
    JSONString := Streamer.ObjectToJSONString(o);
    WriteLn(JSONString);
 
  // aufräumen
  finally
    o.Destroy;
    Streamer.Destroy;
  end;
end;

Ausblick

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.

Über die Eigenschaft Options der Klasse TJSONStreamer kann gesteuert werden, wie die Ausgabe die eigenen Datenstrukturen in JSON abgebildet werden.

Siehe auch

Einzelnachweise