2

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?

  • Your question is very verbose. It boils down to `TJson.JsonToObject(..)` (and perhaps `TJson.ObjectToJsonObject(..)` too) not working properly with generic classes like `TList` or `TDictionary`. – Günther the Beautiful Dec 15 '17 at 16:21

1 Answers1

6

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.

Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • 1
    Agreed, default constructor might be the reason for this AV. Without running a single test I've just suspected that `FComparer` might have been serialized and deserialized (as an _invalid_ reference). Pity that Delphi RTL doesn't have some common, let's say `[Serializable]`, `[NonSerializable]` attributes for their classes and fields. – Victoria Dec 16 '17 at 02:11
  • @StefanGlienke : Parameterless constructor to TdictCategory - tested. The test was done correctly, without AV crash. But for each PAIR, all items in the VALUE were empty. Before the final test, based on try/fail method, the original declaration of "TCategParams", should be changed to: "TCategParams = class **aFontStyles**: **Byte**; rgbColor: COLORREF; end;". Regarding json text in SaveToFile part. The contents of the json file appear to be correct –  Dec 16 '17 at 11:12