(プロジェクトは、https://bitbucket.org/OldTPFun/delphitest/src/master/JsonTest/Proj2/ に配置してあります。)
今回読み書きするのは以下のようなファイル。
{"TownName":"木組みの家と石畳の街", "Shops":{ "ラビットハウス": {"ShopName":"ラビットハウス", "Clerks":[ {"Name":"ココア","Age":17}, {"Name":"チノ","Age":15}, {"Name":"リゼ","Age":17} ] }, "甘兎庵": {"ShopName":"甘兎庵", "Clerks":[ {"Name":"チヤ","Age":17} ] } } }
先ずは書き込みから。
今回は、Keyが文字列で、ValueがTCoffeeShop型のTDictionaryをシリアライズ・デシリアライズすればよいので、前回にならって、TDictionary
- TCoffieShopDicConverter = class(TJsonDictionaryConverter<string, TCoffeeShop>);
を作成して、上記のJSON文字列にシリアライズするためのクラスを作成して、TDictionary
- [JsonSerialize(TJsonMemberSerialization.&Public)]
- TCofeeShopList = class
- private
- FTownName: String;
- FShops: TObjectDictionary<string, TCoffeeShop>;
- procedure SetTownName(const Value: String);
- procedure SetShops(const Value: TObjectDictionary<string, TCoffeeShop>);
- public
- property TownName : String read FTownName write SetTownName;
- [JsonConverter(TCoffieShopDicConverter)]
- property Shops : TObjectDictionary<string, TCoffeeShop> read FShops write SetShops;
- constructor Create;
- end;
で、シリアライズするためのコード
- procedure TForm1.Button1Click(Sender: TObject);
- var
- CoffeeShop1,CoffeeShop2 : TCoffeeShop;
- serializer: TJsonSerializer;
- CofeeShopList : TCofeeShopList;
- s : string;
- begin
- CofeeShopList := TCofeeShopList.Create;
- CoffeeShop1 := TCoffeeShop.Create;
- CoffeeShop2 := TCoffeeShop.Create;
- try
- CoffeeShop1.ShopName := 'ラビットハウス';
- CoffeeShop1.Clerks.Add(TClerk.Create('ココア',17));
- CoffeeShop1.Clerks.Add(TClerk.Create('チノ',15));
- CoffeeShop1.Clerks.Add(TClerk.Create('リゼ',17));
- CoffeeShop2.ShopName := '甘兎庵';
- CoffeeShop2.Clerks.Add(TClerk.Create('チヤ',17));
- CofeeShopList.TownName := '木組みの家と石畳の街';
- CofeeShopList.Shops.Add(CoffeeShop1.ShopName,CoffeeShop1);
- CofeeShopList.Shops.Add(CoffeeShop2.ShopName,CoffeeShop2);
- serializer := TJsonSerializer.Create;
- try
- s := serializer.Serialize(CofeeShopList);
- Memo1.Text := s;
- TFile.WriteAllText('CoffeeShop.Json',s);
- finally
- serializer.Free;
- end;
- finally
- CofeeShopList.Shops.Clear;
- CofeeShopList.Free;
- end;
- end;
を書いて実行すると、見事に成功と思いきや・・・・
例外クラスEAbstractError (メッセージ'抽象エラー')を送出しました。
と例外が発生。ありゃりゃ。
デバッグ実行した結果、PropertyToKeyの呼び出し時に例外が発生していることが分かったので、System.JSON.Converters.pasのTJsonDictionaryConverter<k,v>の定義を調べてみると、
- TJsonDictionaryConverter<k,v> = class(TJsonConverter)
- protected
- function CreateDictionary: TDictionary<k,v>; virtual;
- function PropertyToKey(const APropertyName: string): K; virtual; abstract;
- function KeyToProperty(const AKey: K): string; virtual; abstract;
- function ReadKey(const AReader: TJsonReader; const ASerializer: TJsonSerializer): string;
- function ReadValue(const AReader: TJsonReader; const ASerializer: TJsonSerializer): V; virtual;
- procedure WriteValue(const AWriter: TJsonWriter; const AValue: V; const ASerializer: TJsonSerializer); virtual;
- public
- procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override;
- function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue;
- const ASerializer: TJsonSerializer): TValue; override;
- function CanConvert(ATypeInf: PTypeInfo): Boolean; override;
- end;
と、PropertyToKey,とKeyToPropertyにabstractがついているので、この2つは、継承先のコンバーターで実装しないといけなかったのね。
System.JSON.Converters.pasに、TJsonDictionaryConverterを継承した、キーが文字列のTJsonStringDictionaryConverter<V>の定義が あるので、中身をみてみると、
- TJsonStringDictionaryConverter<V> = class(TJsonDictionaryConverter<string, V>)
- protected
- function PropertyToKey(const APropertyName: string): string; override;
- function KeyToProperty(const AKey: string): string; override;
- end;
とあって、実装が
- function TJsonStringDictionaryConverter<V>.KeyToProperty(const AKey: string): string;
- begin
- Result := AKey;
- end;
- function TJsonStringDictionaryConverter<V>.PropertyToKey(const APropertyName: string): string;
- begin
- Result := APropertyName;
- end;
となっているので、Dictionaryのコンバーターを作成する時は、TJsonDictionaryConverterから、キーの型を決めた派生型を作成し、KeyToPropertyにはキーから文字列に変換、PropertyToKeyには文字列からキーの型のインスタンスへの変換を自前で実装すれば、良いわけですね。
例えば、キーが整数型のDictionaryのコンバータは
- TJsonIntegerDictionaryConverter<V> = class(TJsonDictionaryConverter<Integer, V>)
- protected
- function PropertyToKey(const APropertyName: string): Integer; override;
- function KeyToProperty(const AKey: Integer): string; override;
- end;
- function TJsonStringDictionaryConverter<V>.KeyToProperty(const AKey: Integer): string;
- begin
- Result := AKey.ToString;
- end;
- function TJsonStringDictionaryConverter<V>.PropertyToKey(const APropertyName: string): Integer;
- begin
- Result := APropertyName.ToInteger;
- end;
とすれば、良いわけだ。
今回は、定義済みの、TJsonStringDictionaryConverterを使用して先ほどの
- TCoffieShopDicConverter = class(TJsonDictionaryConverter<string, TCoffeeShop>);
を
- TCoffieShopDicConverter = class(TJsonStringDictionaryConverter<TCoffeeShop>);
に修正し実行すれば、目的のJSON形式の文字列のファイルが作成できます。
ファイルの読み込みは前回と同様、次のコードになります。(確認用に読み込んだものメモに列挙しております。)
- procedure TForm1.Button2Click(Sender: TObject);
- var
- CoffeeShop : TCoffeeShop;
- CofeeShopList : TCofeeShopList;
- serializer: TJsonSerializer;
- s : string;
- Clerk : TClerk;
- begin
- s := TFile.ReadAllText('CoffeeShop.Json',TEncoding.UTF8);
- serializer := TJsonSerializer.Create;
- try
- CofeeShopList := serializer.Deserialize<tcofeeshoplist>(s);
- try
- Memo1.Clear;
- Memo1.Lines.Add(CofeeShopList.TownName);
- for CoffeeShop in CofeeShopList.Shops.Values do
- begin
- Memo1.Lines.Add(CoffeeShop.ShopName);
- for Clerk in CoffeeShop.Clerks do
- begin
- Memo1.Lines.Add(Clerk.Name + '(' + Clerk.Age.ToString() + ')');
- end;
- end;
- finally
- CoffeeShop.Clerks.Clear();
- CoffeeShop.Free;
- end;
- finally
- serializer.Free
- end;