Serialisieren von Benutzerobjekten

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu DataSnap-Anwendungen entwickeln


Es ist jetzt möglich, mit JSON-Objekten benutzerdefinierte Objekte zwischen Client- und Servermethoden zu übergeben.

Das Paradigma basiert auf dem Marshaling von Benutzerobjekten in JSON-Objekte und dann auf der Gegenseite zurück in Benutzerobjekte. DataSnap stellt in der Unit DBXJSONReflect mit der Klasse TTypeMarshaller eine generische Serialisierungsfolge bereit. Benutzerobjekte können auf Basis einer Reihe von Konverter- und Reverter-Klassen in eine äquivalente Repräsentation und dann zurück in Benutzerinstanzen umgewandelt werden.

TJSONMarshal und TJSONUnMarshal sind zwei Implementierungen der Serialisierung auf Basis von JSON-Objekten: Benutzerobjekte werden in äquivalente JSON-Objekte und diese wieder zurück in Benutzerobjekte umgewandelt.

Nicht alle benutzerdefinierten Objekte können nur auf Basis von Marshaling-Klassen serialisiert werden. Sie müssen eventuell mit benutzerdefinierten Konvertern für über RTTI nicht korrekt serialisierbare Benutzerfelder erweitert werden. Eine Konvertierung kann einem Typ oder einem Feld der Benutzerklasse zugeordnet werden. Es gibt vier Konvertertypen für jede Kategorie, die in der folgenden Tabelle aufgeführt sind:

Beschreibung Feld Typ
Konvertierung in einen String TStringConverter TTypeStringConverter
Konvertierung in ein Objekt TObjectConverter TTypeObjectConverter
Konvertierung in ein String-Array TStringsConverter TTypeStringsConverter
Konvertierung in ein Objekt-Array TObjectsConverter TTypeObjectsConverter

Das Konverter/Reverter-Prinzip basiert auf der Datenumwandlung in eine Repräsentation, aus der die Instanz wiederhergestellt werden kann. Sie können zwischen der Konvertierung einer komplexen Datenstruktur in einen einfachen String, der zerlegt und wieder zurückkonvertiert werden kann, oder einem komplizierteren aber effizienteren Verfahren auswählen. Angenommen, eine Objektkollektion soll serialisiert werden. Eine Möglichkeit dazu wäre, jedes Element in einen String umzuwandeln und alle Strings mit einem eindeutigen Trennzeichen zu verketten. Ein effizienterer Weg ist die Konvertierung der Kollektion in ein Objekt-Array. Der Reverter erhält dieses Array als Eingabe und kann die komplexe Kollektion wiederherstellen.

Das Codebeispiel auf dieser Seite wurde zum leichteren Verständnis in Blöcke unterteilt. Das Beispiel ist Teil eines {{Product}-Bibliothek-Projekts, das Sie erstellen müssen, damit das Beispiel ausgeführt werden kann, und Sie Ergebnisse sehen können. Der Code auf dieser Seite sollte im selben Quellcode (.pas-Datei) wie das Hauptformular des Projekts enthalten sein.

 {$R *.res}
 
 uses
   SysUtils, Classes,JSON,DBXJSON, DBXJSONReflect;

Das folgende Codebeispiel zum Serialisieren von benutzerdefinierten Typen veranschaulicht dieses Vorgehen.

 type
 TForm1 = class(TForm)
 Memo1: TMemo;
 Button1: TButton;
 procedure Button1Click(Sender: TObject);
 procedure MainProc;

 private
  { Private declarations }
 public
  { Public declarations }
  end;

   TAddress = record
     FStreet: String;
     FCity: String;
     FCode: String;
     FCountry: String;
     FDescription: TStringList;
   end;
 
   TPerson = class
   private
     FName: string;
     FHeight: integer;
     FAddress: TAddress;
     FSex: char;
     FRetired: boolean;
     FChildren: array of TPerson;
     FNumbers: set of 1..10;
   public
     constructor Create;
     destructor Destroy; override;
 
     procedure AddChild(kid: TPerson);
   end;

In dem Beispiel werden die folgenden Variablen verwendet:

 var
   m: TJSONMarshal;
   unm: TJSONUnMarshal;

Die Deklarationen der TPerson-Klassen-Member:

 constructor TPerson.Create;
 begin
   inherited;
   FAddress.FDescription := TStringList.Create;
 end;
 
 destructor TPerson.Destroy;
 begin
   
   FAddress.FDescription.Free;
  inherited;
 end;
 
 procedure TPerson.AddChild(kid: TPerson);
 begin
   SetLength(FChildren, Length(FChildren) + 1);
   FChildren[Length(FChildren) - 1] := kid;
 end;

Das Beispiel enthält komplexe Kollektionstypen (TStringList), Mengen, Arrays und Records. FNumbers soll ein transientes Feld (Vorgabe für Mengen) sein.

Die Hauptprogrammprozedur:

 procedure TForm1.MainProc;
 var
   person,newperson: TPerson;
   kid: TPerson;
   JSONString: String;
 
 begin
   m := TJSONMarshal.Create(TJSONConverter.Create);
   unm := TJSONUnMarshal.Create;
 
   { For each complex field type, we will define a converter/reverter pair. We will individually deal
     with the "FChildren" array, the "TStringList" type, and the "FAddress" record. We will transform
     the array type into an actual array of "TPerson", as illustrated below. }
 
   m.RegisterConverter(TPerson, 'FChildren', function(Data: TObject; Field: String): TListOfObjects
   var
     obj: TPerson;
     I: Integer;
 
   begin
     SetLength(Result, Length(TPerson(Data).FChildren));
     I := Low(Result);
     for obj in TPerson(Data).FChildren do
     begin
       Result[I] := obj;
       Inc(I);
     end;
   end);
 
   { The implementation is quite straightforward: each child "TPerson" is appended to a predefined
     type instance "TListOfObjects". Later on, each of these objects will be serialized by the same
     marshaller and added to a "TJSONArray" instance. The reverter will receive as argument a
     "TListOfObjects" being oblivious of the "TJSONArray" used for that. }
 
   { For "TStringList", we will have a generic converter that can be reused for other marshal instances.
     The converter simply returns the array of strings of the list. }
 
   { Note that this converter is not really needed here because the TStringList converter is already
     implemented in the Marshaller. }
 
   m.RegisterConverter(TStringList, function(Data: TObject): TListOfStrings
   var
     i, count: integer;
 
   begin
     count := TStringList(Data).Count;
     SetLength(Result, count);
     for I := 0 to count - 1 do
       Result[i] := TStringList(Data)[i];
   end);
 
   { Finally, the address record will be transformed into an array of strings, one for each record
     field with the description content at the end of it. }
 
   m.RegisterConverter(TPerson, 'FAddress', function(Data: TObject; Field: String): TListOfStrings
   var
     Person: TPerson;
     I: Integer;
     Count: Integer;
 
   begin
     Person := TPerson(Data);
     if Person.FAddress.FDescription <> nil then
       Count := Person.FAddress.FDescription.Count
     else
       Count := 0;
     SetLength(Result, Count + 4);
     Result[0] := Person.FAddress.FStreet;
     Result[1] := Person.FAddress.FCity;
     Result[2] := Person.FAddress.FCode;
     Result[3] := Person.FAddress.FCountry;
     for I := 0 to Count - 1 do
       Result[4+I] := Person.FAddress.FDescription[I];
   end);
 
   { It is easy to imagine the reverter's implementation, present below in bulk. }
 
   unm.RegisterReverter(TPerson, 'FChildren', procedure(Data: TObject; Field: String; Args: TListOfObjects)
   var
     obj: TObject;
     I: Integer;
 
   begin
     SetLength(TPerson(Data).FChildren, Length(Args));
     I := Low(TPerson(Data).FChildren);
     for obj in Args do
     begin
       TPerson(Data).FChildren[I] := TPerson(obj);
       Inc(I);
     end
   end);
 
   { Note that this reverter is not really needed here because the TStringList reverter is already
     implemented in the Unmarshaller. }
 
   unm.RegisterReverter(TStringList, function(Data: TListOfStrings): TObject
   var
     StrList: TStringList;
     Str: string;
 
   begin
     StrList := TStringList.Create;
     for Str in Data do
       StrList.Add(Str);
     Result := StrList;
   end);
 
   unm.RegisterReverter(TPerson, 'FAddress', procedure(Data: TObject; Field: String; Args: TListOfStrings)
   var
     Person: TPerson;
     I: Integer;
 
   begin
     Person := TPerson(Data);
     if Person.FAddress.FDescription <> nil then
       Person.FAddress.FDescription.Clear
     else if Length(Args) > 4 then
       Person.FAddress.FDescription := TStringList.Create;
 
     Person.FAddress.FStreet := Args[0];
     Person.FAddress.FCity := Args[1];
     Person.FAddress.FCode := Args[2];
     Person.FAddress.FCountry := args[3];
     for I := 4 to Length(Args) - 1 do
       Person.FAddress.FDescription.Add(Args[I]);
   end);
 
   { The test code is as follows. }
 
   person := TPerson.Create;
   person.FName := 'John Doe';
   person.FHeight := 167;
   person.FSex := 'M';
   person.FRetired := false;
   person.FAddress.FStreet := '62 Peter St';
   person.FAddress.FCity := 'TO';
   person.FAddress.FCode := '1334566';
   person.FAddress.FDescription.Add('Driving directions: exit 84 on highway 66');
   person.FAddress.FDescription.Add('Entry code: 31415');
 
   kid := TPerson.Create;
   kid.FName := 'Jane Doe';
   person.AddChild(kid);
 
   { Marshal the "person" as a JSONValue and display its contents. }
 
   JSONString := m.Marshal(person).ToString;
   Memo1.Lines.Clear;
   Memo1.Lines.Add(JSONString);
   Memo1.Lines.Add('-----------------------');
 
   { Unmarshal the JSONString to a TPerson class }
 
   newperson := unm.Unmarshal(TJSONObject.ParseJSONValue(JSONString)) as TPerson;
   Memo1.Lines.Add(newperson.FName);
   Memo1.Lines.Add(IntToStr(newperson.FHeight));
 
   { and so on for the other fields }
 end;

Und die Schaltfläche zum Aufrufen von MainProc:

 procedure TForm1.Button1Click(Sender: TObject);
 begin
   MainProc;
 end;

Die JSON-Zwischenrepräsentation für den Testcode in dem obigen Codebeispiel lautet:

 {"type":"Converter.TPerson",
  "id":1,
  "fields":{"FName":"John Doe",
            "FHeight":167,
            "FAddress":["62 Peter St","TO","1334566","","Driving directions: exit 84 on highway 66","Entry code: 31415"],
            "FSex":"M",
            "FRetired":false,
            "FChildren":[{"type":"Converter.TPerson",
                          "id":2,
                          "fields":{"FName":"Jane Doe",
                                    "FHeight":0,
                                    "FAddress":["","","",""],
                                    "FSex":"",
                                    "FRetired":false,
                                    "FChildren":[]
                                   }
                         }
                        ]
           }
 }

Der Erfolg der Serialisierung kann durch Sicherstellung, dass alle Felder serialisiert wurden, überprüft werden. Dazu wird Marshal-Methode HasWarnings überprüft.

Hinweis: Die Serialisierung löst zirkuläre Referenzen auf.

Siehe auch