Сохранение/загрузка словаря Delphi. TDictionary не сериализуется?

TDictionary: SaveToFile / LoadFromFile

Какое элегантное решение! Начнем с того, что все работает так, как ожидалось.

Содержимое сохраняется в файл в формате JSON, который выглядит правильно. Но после перезагрузки файла возникает проблема:

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;

Затем следует испытание. И вся слава кончается. Вот код, включая комментарий:

..
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

Похоже, что Delphi (Берлин 10.1) не может сериализовать словарь! Если это правда, мне действительно больно. Я считаю, что есть много других. Или в прикрепленном коде есть ошибка?


person Community    schedule 15.12.2017    source источник
comment
Ваш вопрос очень многословен. Это сводится к тому, что TJson.JsonToObject<T>(..) (и, возможно, TJson.ObjectToJsonObject<T>(..) тоже) не работает должным образом с универсальными классами, такими как TList<T> или TDictionary<T>.   -  person Günther the Beautiful    schedule 15.12.2017


Ответы (1)


TJson.JsonToObject в конечном итоге создаст экземпляры объектов, используя их конструктор по умолчанию (см. REST.JsonReflect.TJSONUnMarshal.ObjectInstance).

Теперь загляните в System.Generics.Collections и вы увидите, что TDictionary<TKey,TValue> не имеет конструктора по умолчанию (нет, RTTI не имеет информации о значениях параметров по умолчанию, поэтому конструктор с Capacity: Integer = 0 не будет рассматриваться).

Это означает, что RTTI будет искать дальше, найдет TObject.Create и вызовет его в классе словаря, что оставит вас с наполовину инициализированным объектом (без запуска вашего кода я предполагаю, что его FComparer не назначено, что конструктор TDictionary<TKey,TValue> сделал бы).

Короче говоря: добавьте конструктор без параметров в свой TdictCategory и просто вызовите там inherited Create;. Затем TJSONUnMarshal.ObjectInstance найдет конструктор без параметров и вызовет весь код, необходимый для правильной инициализации экземпляра.

В любом случае вы, вероятно, не будете удовлетворены результатом, поскольку REST.JsonReflect просто сериализует все внутренние состояния экземпляров (если только они явно не исключены через атрибуты, которые не выполняются в классах RTL) и, таким образом, также десериализует их, что означает, что такой JSON является только Delphi совместим с Delphi.

person Stefan Glienke    schedule 15.12.2017
comment
Согласен, конструктор по умолчанию может быть причиной этого AV. Не запустив ни одного теста, я только что заподозрил, что FComparer мог быть сериализован и десериализован (как недействительная ссылка). Жаль, что Delphi RTL не имеет каких-то общих, скажем, [Serializable], [NonSerializable] атрибутов для своих классов и полей. - person Victoria; 16.12.2017
comment
@StefanGlienke: конструктор без параметров для TdictCategory - проверено. Тест был сделан правильно, без сбоев AV. Но для каждой ПАРЫ все элементы в ЗНАЧЕНИИ были пусты. Перед окончательным тестом, основанным на методе try/fail, исходное объявление TCategParams должно быть изменено на: TCategParams = class aFontStyles: Byte; rgbColor: ЦВЕТОВАЯ ИНФОРМАЦИЯ; конец;. Относительно текста json в части SaveToFile. Содержимое файла json кажется правильным - person ; 16.12.2017