表示: Delphi C++
表示設定

ユーザー オブジェクトのシリアル化

提供:RAD Studio XE2
移動: 案内, 検索

DataSnap アプリケーションの開発 への移動


JSON オブジェクトを使ってクライアント メソッドとサーバー メソッドの間でユーザー定義オブジェクトを受け渡すことが可能になりました。

このパラダイムは、ユーザー オブジェクトを JSON オブジェクトにマーシャリングし、その後、相手側で元のユーザー オブジェクトに戻すという操作がベースとなっています。DataSnap では、汎用のシリアル化スイートが DBXJSONReflect ユニットに用意されており、TTypeMarshaller クラスを通じて提供されます。変換コンポーネントクラスと逆変換コンポーネントクラスのスイートに基づいて、ユーザー オブジェクトを同等の表現に変換し、その後、元のユーザー インスタンスに逆変換することができます。

TJSONMarshalTJSONUnMarshal の 2 つは、JSON オブジェクトに基づいたシリアル化をそのまま使える形に実装したものです。ユーザー オブジェクトは同等の JSON オブジェクトに変換され、それが元のユーザー オブジェクトに逆変換されるのです。

すべてのユーザー定義オブジェクトがマーシャリング クラスだけに基づいてシリアル化できるわけではありません。RTTI を通じて適切にシリアル化できないユーザー フィールドについては、カスタム変換コンポーネントで拡張しなければならない場合があります。変換を型に関連付けることも、ユーザー クラスのフィールドに関連付けることもできます。カテゴリ別に 4 種類の変換コンポーネントがあります。それらの一覧を以下の表に示します。

説明 フィールド 種類
文字列への変換 TStringConverter TTypeStringConverter
オブジェクトへの変換 TObjectConverter TTypeObjectConverter
文字列配列への変換 TStringsConverter TTypeStringsConverter
オブジェクト配列への変換 TObjectsConverter TTypeObjectsConverter

変換コンポーネント/逆変換コンポーネントの方式は、インスタンスを復元できる表現へのデータ変換に基づいています。複雑なデータ構造を、解析して逆変換可能な単純な文字列に変換することもできますし、場合によっては、もっと高度ながら効率的な方法を選択することもできます。たとえば、オブジェクト コレクションをシリアル化する必要があるとしましょう。各要素を文字列に変換し、それらすべての文字列を 1 種類の区切り記号で連結するというアプローチがまず考えられます。もっと効率的な方法は、コレクションをその中のオブジェクトを要素とする配列に変換することです。逆変換コンポーネントはこの配列を入力として受け取り、複合コレクションを復元できます。

このページで紹介するサンプル コードは、理解しやすいようブロックに分けてあります。このサンプルは、実行して結果を確認するために作成しなければならない VCL プロジェクトの一部です。このページのコードは、プロジェクトのメイン フォームと同じソース コード(.pas ファイル)に含める必要があります。

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

以下のユーザー定義型をシリアル化するサンプル コードについて解説します。

 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;

例では以下の変数を使用します。

 var
   m: TJSONMarshal;
   unm: TJSONUnMarshal;

TPerson のクラス メンバの宣言は次のようになっています。

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

この例には、複合コレクション型(TStringList)、セット、配列、レコードが含まれています。ここでは、FNumbers を一時フィールド(セットの場合のデフォルト)と見なすことにしました。

メイン プログラムの手続きは次のとおりです。

 procedure MainProc;
 var
   person: 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.Name);
   Memo1.Lines.Add(IntToStr(newperson.Height));
 
   { and so on for the other fields }
 end;

MainProc を呼び出すボタンは次のようになっています。

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

上記サンプル コード内のテスト コードの JSON 形式中間表現は次のようになります。

 {"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":[]
                                   }
                         }
                        ]
           }
 }

シリアル化に成功したかどうかは、すべてのフィールドがシリアル化されたかどうかをマーシャリング クラスの HasWarnings メソッドで確認することでチェックできます。

メモ: シリアル化処理では循環参照を解決できます。

関連項目

以前のバージョン
他言語版