Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

TJsonSerializerの実用例

TFormなどを継承したクラスの場合

例えば起動時と終了時にフォームの位置を記憶・復元することを考えた場合、TJsonSerializerにTForm1などのインスタンスを渡すことになりますが、デフォルトでは余計なデータまで保存されてしまうため、保存項目を自分で指定するためにまずTForm1にJsonSerialize(TJsonMemberSerialization.In)というカスタム属性を指定し、次に保存したいメンバにJsonIn属性を持たせることで任意の項目のみをシリアライズすることになります。
しかし自分で定義した型と違い、TFormの派生クラスでは親クラスより上の段階でLeftやTopといったプロパティが定義されているため、ぱっと見属性を書く場所がありません。そういった場合はプロパティを再定義し、そこに属性を記述します。

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  published
    [JsonIn]
    property Left; // <==再定義
    [JsonIn]
    property Top; // <==再定義
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  serializer: TJsonSerializer;
begin
  if FileExists('config.json') then
  begin
    serializer := TJsonSerializer.Create;
    try
      serializer.Populate(TFile.ReadAllText('config.json', TEncoding.UTF8), Self);
    finally
      serializer.Free;
    end;
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  serializer: TJsonSerializer;
begin
  serializer := TJsonSerializer.Create;
  try
    TFile.WriteAllText('config.json', serializer.Serialize(Self), TEncoding.UTF8);
  finally
    serializer.Free;
  end;
end;

ちなみに属性を何も指定せずにTFormのインスタンスシリアライズしようとすると、スタックオーバーフローが発生します。これはTFormのプロパティを辿っていくと再び自身(TForm)が出てくる場合があり、無限ループになってしまうせいです。そのため必ず属性による制御が必要です。

定義済みの型の一部のみをシリアライズしたい場合

先の例は継承したクラスがあるため自分で属性付けが可能でしたが、継承せずそのまま使うクラスであったり、そもそも継承が不可能なレコードの場合、直接カスタム属性を付与することはできません。この場合は少し面倒になりますが、TJsonDynamicContractResolverを使用して動的に属性を付与することになります。
例えばTRectをシリアライズしようと考えた場合、そのままシリアライズするとLeft, Top, Right, Bottomだけではなく、可変部分であるTopLeftとBottomRightも一緒に出力されてしまいます。そこでLeft, Top, Right, Bottomのみをシリアライズするような場合を考えます。

var
  rect: TRect;
  serializer: TJsonSerializer;
  resolver: TJsonDynamicContractResolver;
begin
  rect.Left := 100;
  rect.Top := 200;
  rect.Right := 300;
  rect.Bottom := 400;

  serializer := TJsonSerializer.Create;
  try
    resolver := TJsonDynamicContractResolver.Create;
    serializer.ContractResolver := resolver;
    resolver.SetFieldsIgnored(TypeInfo(TRect), ['TopLeft', 'BottomRight']);
    TFile.WriteAllText('rect.json', serializer.Serialize(rect), TEncoding.UTF8);
  finally
    serializer.Free;
  end;
end;

何も指定せずにシリアライズすると{"Left":100,"Top":200,"Right":300,"Bottom":400,"TopLeft":{"X":100,"Y":200},"BottomRight":{"X":300,"Y":400}}となってしまいますが、このコードでシリアライズすると{"Left":100,"Top":200,"Right":300,"Bottom":400}のみが得られます。
なお、ContractResolverプロパティはインターフェース型で、代入した時点で自動管理になるのでresolverを自身で解放する必要はありません。