Sérialisation des objets utilisateur
Remonter à Développement d'applications DataSnap
Il est maintenant possible de passer des objets définis par l'utilisateur entre les méthodes client et serveur en utilisant des objets JSON.
Le paradigme est basé sur le marshaling des objets utilisateur en objets JSON, puis de nouveau en objets utilisateur sur le côté opposé. DataSnap fournit une suite de sérialisations génériques dans l'unité DBXJSONReflect par le biais de la classe TTypeMarshaller. Les objets utilisateur peuvent être transformés en une représentation équivalente, puis reconvertis en instances utilisateur selon une suite de classes converter et reverter.
TJSONMarshal et TJSONUnMarshal sont deux implémentations de la sérialisation basée sur des objets JSON : les objets utilisateur sont transformés en objets JSON équivalents, qui sont reconvertis en objets utilisateur.
Les objets définis par l'utilisateur ne peuvent pas tous être sérialisés uniquement selon les classes de marshaling. Ils peuvent être étendus avec des convertisseurs personnalisés pour les champs utilisateur qui ne peuvent pas être correctement sérialisés par le biais de RTTI. Une conversion peut être associée à un type ou un champ de la classe utilisateur. Les quatre types de convertisseurs qui existent pour chaque catégorie sont listés dans le tableau suivant.
| Description | Champ | Type |
|---|---|---|
| Conversion en une chaîne | TStringConverter | TTypeStringConverter |
| Conversion en un objet | TObjectConverter | TTypeObjectConverter |
| Conversion en un tableau de chaînes | TStringsConverter | TTypeStringsConverter |
| Conversion en un tableau d'objets | TObjectsConverter | TTypeObjectsConverter |
Le principe convertisseur/restaurateur est basé sur la transformation des données en représentation à partir de laquelle l'instance peut être restaurée. Il est possible de choisir la conversion d'une structure de données complexe en une chaîne simple qui peut être analysée pour être restaurée, ou une méthode plus sophistiquée mais plus efficace peut être choisie selon le cas. Supposons qu'une collection d'objets doit être sérialisée. Une approche peut consister à transformer chaque élément en une chaîne et à concaténer toutes les chaînes avec un séparateur unique. Une méthode plus efficace consiste à convertir la collection en un tableau d'objets. Le restaurateur reçoit ce tableau en entrée et peut reconstituer la collection complexe.
Nous allons commenter un exemple de code qui sérialise les types définis par l'utilisateur ci-dessous.
type
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;
L'exemple comporte des types de collection complexes (TStringList), des ensembles, des tableaux et des enregistrements. Nous avons choisi de considérer FNumbers comme un champ transitoire (par défaut pour un ensemble).
L’exemple utilise les variables suivantes :
var m: TJSONMarshal; unm: TJSONUnMarshal;
chacune instanciée comme suit :
m := TJSONMarshal.Create(TJSONConverter.Create); unm := TJSONUnMarshal.Create;
Pour chaque type de champ complexe, nous allons définir une paire convertisseur/restaurateur. Nous allons traiter individuellement le tableau FChildren, le type TStringList et l'enregistrement FAddress. Nous allons transformer le type tableau en un tableau réel de TPerson, comme illustré ci-dessous :
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);
L'implémentation est tout ce qu'il y a de plus simple : chaque TPerson enfant est ajouté à une instance de type prédéfini TListOfObjects. Par la suite, chacun de ces objets sera sérialisé par le même marshaller et ajouté à une instance TJSONArray. Le restaurateur recevra en argument un TListOfObjects en oubliant le TJSONArray utilisé pour cela.
Pour le TStringList, nous aurons un convertisseur générique qui peut être réutilisé pour les autres instances de marshal. Le convertisseur renvoie simplement le tableau de chaînes de la liste.
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);
Enfin, l'enregistrement adresse sera transformé en un tableau de chaînes, une pour chaque champ d'enregistrement avec le contenu de la description à la fin.
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);
Il est facile d'imaginer l'implémentation du restaurateur, présent en grande partie ci-dessous :
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);
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);
Pour un code test comme celui-ci :
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);
la représentation JSON intermédiaire est :
{"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":[]
}
}
]
}
}
Pour v: TJSONValue;, nous pouvons transformer un person vers et depuis un objet JSON :
... v := m.Marshal(person); ... person := unm.Unmarshal(v); ...
La réussite de la sérialisation peut être vérifiée en s'assurant que tous les champs sont sérialisés, avec la méthode HasWarnings du marshal.
Remarque : Le processus de sérialisation résout les références circulaires.