Serialisieren von Benutzerobjekten
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.