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

提供: RAD Studio
移動先: 案内検索

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


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

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

TJSONMarshalTJSONUnMarshal は、JSON オブジェクトをベースとしたシリアル化のための、2 つのすぐに使える実装です: ユーザー オブジェクトは同等の JSON オブジェクトに変換され、それが元のユーザー オブジェクトに逆変換されるのです。

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

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

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

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

{$R *.res}

uses
  SysUtils, Classes, JSON, DBXJSON, DBXJSONReflect;

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

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;

この例では次の変数を使用しています:

var
  m: TJSONMarshal;
  unm: TJSONUnMarshal;

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

constructor TPerson.Create;
begin
  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;

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

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

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;

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 メソッドで確認することでチェックできます。

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

関連項目