2018年5月19日土曜日

TokyoでDictionaryを要素にもつJson形式のファイルの読み書きをしてみた。

前回、LIST構造を持つJSON形式のファイルを読み書きしたので、今回はDictionary構造を持つファイルを読み書きしてみた。
(プロジェクトは、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用のコンバータ

  1. TCoffieShopDicConverter = class(TJsonDictionaryConverter<string, TCoffeeShop>);  

を作成して、上記のJSON文字列にシリアライズするためのクラスを作成して、TDictionary型の変数にTCoffieShopDicConverter属性を設定

  1. [JsonSerialize(TJsonMemberSerialization.&Public)]  
  2. TCofeeShopList = class  
  3. private  
  4.   FTownName: String;  
  5.   FShops: TObjectDictionary<string, TCoffeeShop>;  
  6.   procedure SetTownName(const Value: String);  
  7.   procedure SetShops(const Value: TObjectDictionary<string, TCoffeeShop>);  
  8.   public  
  9.     property TownName : String read FTownName write SetTownName;  
  10.     [JsonConverter(TCoffieShopDicConverter)]  
  11.     property Shops : TObjectDictionary<string, TCoffeeShop> read FShops write SetShops;  
  12.     constructor Create;  
  13. end;  

で、シリアライズするためのコード

  1. procedure TForm1.Button1Click(Sender: TObject);  
  2. var  
  3.   CoffeeShop1,CoffeeShop2 : TCoffeeShop;  
  4.   serializer: TJsonSerializer;  
  5.   CofeeShopList : TCofeeShopList;  
  6.   s : string;  
  7. begin  
  8.   CofeeShopList := TCofeeShopList.Create;  
  9.   CoffeeShop1 := TCoffeeShop.Create;  
  10.   CoffeeShop2 := TCoffeeShop.Create;  
  11.   try  
  12.     CoffeeShop1.ShopName := 'ラビットハウス';  
  13.     CoffeeShop1.Clerks.Add(TClerk.Create('ココア',17));  
  14.     CoffeeShop1.Clerks.Add(TClerk.Create('チノ',15));  
  15.     CoffeeShop1.Clerks.Add(TClerk.Create('リゼ',17));  
  16.   
  17.     CoffeeShop2.ShopName := '甘兎庵';  
  18.     CoffeeShop2.Clerks.Add(TClerk.Create('チヤ',17));  
  19.   
  20.     CofeeShopList.TownName := '木組みの家と石畳の街';  
  21.     CofeeShopList.Shops.Add(CoffeeShop1.ShopName,CoffeeShop1);  
  22.     CofeeShopList.Shops.Add(CoffeeShop2.ShopName,CoffeeShop2);  
  23.   
  24.     serializer := TJsonSerializer.Create;  
  25.     try  
  26.   
  27.       s := serializer.Serialize(CofeeShopList);  
  28.       Memo1.Text := s;  
  29.   
  30.       TFile.WriteAllText('CoffeeShop.Json',s);  
  31.   
  32.     finally  
  33.       serializer.Free;  
  34.     end;  
  35.   
  36.   finally  
  37.     CofeeShopList.Shops.Clear;  
  38.     CofeeShopList.Free;  
  39.   end;  
  40. end;  

を書いて実行すると、見事に成功と思いきや・・・・

例外クラスEAbstractError (メッセージ'抽象エラー')を送出しました。

と例外が発生。ありゃりゃ。

デバッグ実行した結果、PropertyToKeyの呼び出し時に例外が発生していることが分かったので、System.JSON.Converters.pasのTJsonDictionaryConverter<k,v>の定義を調べてみると、
  1. TJsonDictionaryConverter<k,v> = class(TJsonConverter)  
  2.   protected  
  3.     function CreateDictionary: TDictionary<k,v>; virtual;  
  4.     function PropertyToKey(const APropertyName: string): K; virtual; abstract;  
  5.     function KeyToProperty(const AKey: K): string; virtual; abstract;  
  6.     function ReadKey(const AReader: TJsonReader; const ASerializer: TJsonSerializer): string;  
  7.     function ReadValue(const AReader: TJsonReader; const ASerializer: TJsonSerializer): V; virtual;  
  8.     procedure WriteValue(const AWriter: TJsonWriter; const AValue: V; const ASerializer: TJsonSerializer); virtual;  
  9.   public  
  10.     procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override;  
  11.     function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue;  
  12.       const ASerializer: TJsonSerializer): TValue; override;  
  13.     function CanConvert(ATypeInf: PTypeInfo): Boolean; override;  
  14.   end;  

と、PropertyToKey,とKeyToPropertyにabstractがついているので、この2つは、継承先のコンバーターで実装しないといけなかったのね。

System.JSON.Converters.pasに、TJsonDictionaryConverterを継承した、キーが文字列のTJsonStringDictionaryConverter<V>の定義が あるので、中身をみてみると、

  1. TJsonStringDictionaryConverter<V> = class(TJsonDictionaryConverter<string, V>)  
  2. protected  
  3.   function PropertyToKey(const APropertyName: string): string; override;  
  4.   function KeyToProperty(const AKey: string): string; override;  
  5. end;  

とあって、実装が

  1. function TJsonStringDictionaryConverter<V>.KeyToProperty(const AKey: string): string;  
  2. begin  
  3.   Result := AKey;  
  4. end;  
  5.   
  6. function TJsonStringDictionaryConverter<V>.PropertyToKey(const APropertyName: string): string;  
  7. begin  
  8.   Result := APropertyName;  
  9. end;  

となっているので、Dictionaryのコンバーターを作成する時は、TJsonDictionaryConverterから、キーの型を決めた派生型を作成し、KeyToPropertyにはキーから文字列に変換、PropertyToKeyには文字列からキーの型のインスタンスへの変換を自前で実装すれば、良いわけですね。
例えば、キーが整数型のDictionaryのコンバータは

  1.   TJsonIntegerDictionaryConverter<V> = class(TJsonDictionaryConverter<Integer, V>)  
  2.   protected  
  3.     function PropertyToKey(const APropertyName: string): Integer; override;  
  4.     function KeyToProperty(const AKey: Integer): string; override;  
  5.   end;  
  6.   
  7. function TJsonStringDictionaryConverter<V>.KeyToProperty(const AKey: Integer): string;  
  8. begin  
  9.   Result := AKey.ToString;  
  10. end;  
  11.   
  12. function TJsonStringDictionaryConverter<V>.PropertyToKey(const APropertyName: string): Integer;  
  13. begin  
  14.   Result := APropertyName.ToInteger;  
  15. end;  

とすれば、良いわけだ。

今回は、定義済みの、TJsonStringDictionaryConverterを使用して先ほどの

  1. TCoffieShopDicConverter = class(TJsonDictionaryConverter<string, TCoffeeShop>);  


  1. TCoffieShopDicConverter = class(TJsonStringDictionaryConverter<TCoffeeShop>);  

に修正し実行すれば、目的のJSON形式の文字列のファイルが作成できます。

ファイルの読み込みは前回と同様、次のコードになります。(確認用に読み込んだものメモに列挙しております。)

  1. procedure TForm1.Button2Click(Sender: TObject);  
  2. var  
  3.   CoffeeShop : TCoffeeShop;  
  4.   CofeeShopList : TCofeeShopList;  
  5.   serializer: TJsonSerializer;  
  6.   s : string;  
  7.   Clerk : TClerk;  
  8. begin  
  9.   
  10.   s := TFile.ReadAllText('CoffeeShop.Json',TEncoding.UTF8);  
  11.   
  12.   serializer := TJsonSerializer.Create;  
  13.   try  
  14.     CofeeShopList := serializer.Deserialize<tcofeeshoplist>(s);  
  15.     try  
  16.       Memo1.Clear;  
  17.       Memo1.Lines.Add(CofeeShopList.TownName);  
  18.       for CoffeeShop in CofeeShopList.Shops.Values do  
  19.       begin  
  20.         Memo1.Lines.Add(CoffeeShop.ShopName);  
  21.         for Clerk in CoffeeShop.Clerks do  
  22.           begin  
  23.           Memo1.Lines.Add(Clerk.Name + '(' + Clerk.Age.ToString() + ')');  
  24.         end;  
  25.       end;  
  26.   
  27.     finally  
  28.       CoffeeShop.Clerks.Clear();  
  29.       CoffeeShop.Free;  
  30.     end;  
  31.   finally  
  32.     serializer.Free  
  33.   end;  

0 件のコメント: