Sérialisation des objets utilisateur

De RAD Studio (Français)

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.

Autres langues