Delphi Dictionary Save/Load. TDictionary not serializable?

1.9k Views Asked by At

TDictionary : SaveToFile / LoadFromFile

What an elegant solution! To begin with, everything runs as expected.

The content is saved to a file in a JSON format that looks right. But after reloading the file, there is a problem:

Type
  TEnumCategTypes = ( e_SQL1, e_VBA, e_Text );
  TCategParams = class
    fontStyles  : TFontStyles;
    rgbColor    : COLORREF;
    end;

  TdictCategory = class ( TDictionary<TEnumCategTypes, TCategParams> )
    public
      public class function LoadFromFile( const AFileName: string ): TdictCategory;
      public class procedure SaveToFile( const AFileName: string; dict: TdictCategory );
    end;

implementation

class procedure TdictCategory.SaveToFile( const AFileName: string; dict: TdictCategory );
var
  stream : TStringStream;
begin
  try
    stream := TStringStream.Create( TJson.ObjectToJsonString( dict ) ) ;
    stream.SaveToFile( AFileName )
  finally
    stream.Free;
  end;
end;
//---
class function TdictCategory.LoadFromFile( const AFileName: string ): TdictCategory;
var
  stream: TStringStream;
begin
  stream   := TStringStream.Create;
  try
    stream.LoadFromFile( AFileName );
    result := TJson.JsonToObject<TdictCategory>( stream.DataString );
  finally
    stream.Free;
  end;
end;

The test follows. And all the glory ends. Here is the code, including the comment:

..
var
  cc: Colorref;
begin
  ..                                                          // fill values 
  cc := DictCategory.Items[ e_SQL1 ].rgbColor;                // Okay, it works
  TdictCategory.SaveToFile( 'category.json', DictCategory );  // Even the contents of the file, looks good 
  DictCategory.Clear;
  DictCategory.Free;
  DictCategory := nil;
  DictCategory := TdictCategory.LoadFromFile( 'category.json' );   // DictCategory is no longer NIL, and it looks optically well..
  cc           := DictCategory.Items[ e2_sql_aggregate ].rgbColor; // C R A S H !!!  with AV

It seems that Delphi (Berlin 10.1), can not serialize the Dictionary! If that's true, it really hurts me. I believe there are many others. Or is there any error in the attached code?

1

There are 1 best solutions below

2
On

TJson.JsonToObject ultimately will instantiate objects using their default constructor (see REST.JsonReflect.TJSONUnMarshal.ObjectInstance).

Now look into System.Generics.Collections and you will see that TDictionary<TKey,TValue> does not have a default constructor (no, RTTI has no information about default values for parameters so the constructor with Capacity: Integer = 0 will not be considered).

This means that RTTI will look further and find TObject.Create and calls that on the dictionary class which will leave you with a half initialized object (without having run your code I guess its FComparer not being assigned which the constructor of TDictionary<TKey,TValue> would have done).

Long story short: add a parameterless constructor to your TdictCategory and just call inherited Create; there. Then TJSONUnMarshal.ObjectInstance will find the parameterless constructor and calls all the code necessary to have a properly initialized instance.

Anyway you probably won't be satisfied with the result as REST.JsonReflect simply serializes all the internal states of instances (unless explicitly excluded via attributes which is not being done in the RTL classes) and thus also deserializes them which means that such JSON is only Delphi-to-Delphi compatible.