Дженерики и маршал / Унмаршал.Что мне здесь не хватает?ЧАСТЬ 2 :-) - PullRequest
4 голосов
/ 06 октября 2011

В продолжение моего предыдущего вопроса: Дженерики и маршал / Унмаршал. Что мне здесь не хватает?

В "part # 1" (ссылка выше) TOndrej предоставил хорошее решение - оно не удалось на XE2. Здесь я приведу исправленный источник, чтобы исправить это.

И я чувствую необходимость еще больше расширить этот вопрос. Поэтому я хотел бы услышать от вас все, как это сделать:

Первое - чтобы запустить источник, работающий на XE2 и XE2 update 1, внесите следующие изменения:

Marshal.RegisterConverter(TTestObject,
  function (Data: TObject): String // <-- String here
  begin
    Result := T(Data).Marshal.ToString; // <-- ToString here
  end
  );

Почему ?? Единственная причина, которую я вижу, должна быть связана с XE2, это иметь намного больше информации RTTI. И, следовательно, он попытается отследить возвращенный объект. Я на правильном пути здесь? Пожалуйста, не стесняйтесь комментировать.

Более важно - в примере не реализован метод UnMarshal. Если кто-то может создать его и опубликовать здесь, я бы с удовольствием: -)

Я надеюсь, что у вас все еще есть интерес к этой теме.

С уважением Бьярне

Ответы [ 2 ]

2 голосов
/ 18 октября 2011

В дополнение к ответу на этот вопрос я опубликовал обходной путь к вашему предыдущему вопросу: Generics and Marshal / UnMarshal.Что мне здесь не хватает?

По какой-то причине использование конструктора TJsonobject не по умолчанию вызывает проблему в XE2 - использование конструктора по умолчанию «исправило» проблему.

Во-первых, вам нужно переместить ваш объект TTestoject в его собственный блок - в противном случае RTTI не сможет найти / создать ваш объект при попытке демаршалировать.

    unit uTestObject;

    interface

    uses
      SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections, DbxJson, DbxJsonReflect;

    type
      {$RTTI EXPLICIT METHODS([]) PROPERTIES([vcPublished]) FIELDS([vcPrivate])}
      TTestObject=class(TObject)
      private
        aList:TStringList;
      public
        constructor Create; overload;
        constructor Create(list: array of string); overload;
        constructor Create(list:TStringList); overload;
        destructor Destroy; override;
        function Marshal:TJSonObject;
        class function Unmarshal(value: TJSONObject): TTestObject;
      published
        property List: TStringList read aList write aList;
      end;

    implementation

    { TTestObject }

    constructor TTestObject.Create;

    begin
      inherited Create;
      aList:=TStringList.Create;
    end;

    constructor TTestObject.Create(list: array of string);

    var
      I:Integer;

    begin
      Create;
      for I:=low(list) to high(list) do
        begin
          aList.Add(list[I]);
        end;
    end;

    constructor TTestObject.Create(list:TStringList);

    begin
      Create;
      aList.Assign(list);
    end;

    destructor TTestObject.Destroy;

    begin
      aList.Free;
      inherited;
    end;

    function TTestObject.Marshal:TJSonObject;

    var
      Mar:TJSONMarshal;

    begin
      Mar:=TJSONMarshal.Create();
      try
        Mar.RegisterConverter(TStringList,
          function(Data:TObject):TListOfStrings

          var
            I, Count:Integer;
          begin
            Count:=TStringList(Data).Count;
            SetLength(Result, Count);
            for I:=0 to Count-1 do
              Result[I]:=TStringList(Data)[I];
          end);
        Result:=Mar.Marshal(Self) as TJSonObject;
      finally
        Mar.Free;
      end;
    end;

    class function TTestObject.Unmarshal(value: TJSONObject): TTestObject;

    var
      Mar: TJSONUnMarshal;
      L: TStringList;

    begin
      Mar := TJSONUnMarshal.Create();
      try
        Mar.RegisterReverter(TStringList,
          function(Data: TListOfStrings): TObject

          var
            I, Count: Integer;
          begin
            Count := Length(Data);
            Result:=TStringList.Create;
            for I := 0 to Count - 1 do
              TStringList(Result).Add(string(Data[I]));
          end
        );
        //UnMarshal will attempt to create a TTestObject from the TJSONObject data
        //using RTTI lookup - for that to function, the type MUST be defined in a unit
        Result:=Mar.UnMarshal(Value) as TTestObject;
      finally
        Mar.Free;
      end;
    end;

    end.

Также обратите внимание, что конструктор был перегружен -это позволяет увидеть, что код функционирует без предварительной накапливания данных в объекте во время создания.

Вот реализация для универсального объекта списка классов

    unit uTestObjectList;

    interface

    uses
      SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections,
      DbxJson, DbxJsonReflect, uTestObject;

    type
      {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
      TTestObjectList<T:TTestObject,constructor> = class(TObjectList<T>)
      public
        function Marshal: TJSonObject;
        constructor Create;
        class function Unmarshal(value: TJSONObject): TTestObjectList<T>; static;
      end;

    //Note: this MUST be present and initialized/finalized so that
    //delphi will keep the RTTI information for the generic class available
    //also, it MUST be "project global" - not "module global"
    var
      X:TTestObjectList<TTestObject>;

    implementation

    { TTestObjectList<T> }
    constructor TTestObjectList<T>.Create;
    begin
      inherited Create;
      //removed the add for test data - it corrupts unmarshaling because the data is already present at creation
    end;

    function TTestObjectList<T>.Marshal: TJSonObject;
    var
      Marshal: TJsonMarshal;
    begin
      Marshal := TJSONMarshal.Create;
      try
        Marshal.RegisterConverter(TTestObjectList<T>,
          function(Data: TObject): TListOfObjects
          var
            I: integer;

          begin
            SetLength(Result,TTestObjectlist<T>(Data).Count);
            for I:=0 to TTestObjectlist<T>(Data).Count-1 do
              Result[I]:=TTestObjectlist<T>(Data)[I];
          end
        );
        Result := Marshal.Marshal(Self) as TJSONObject;
      finally
        Marshal.Free;
      end;
    end;

    class function TTestObjectList<T>.Unmarshal(value: TJSONObject): TTestObjectList<T>;

    var
      Mar: TJSONUnMarshal;
      L: TStringList;

    begin
      Mar := TJSONUnMarshal.Create();
      try
        Mar.RegisterReverter(TTestObjectList<T>,
          function(Data: TListOfObjects): TObject
          var
            I, Count: Integer;
          begin
            Count := Length(Data);
            Result:=TTestObjectList<T>.Create;
            for I := 0 to Count - 1 do
              TTestObjectList<T>(Result).Unmarshal(TJSONObject(Data[I]));
          end
        );
        //UnMarshal will attempt to create a TTestObjectList<TTestObject> from the TJSONObject data
        //using RTTI lookup - for that to function, the type MUST be defined in a unit,
        //and, because it is generic, there must be a GLOBAL VARIABLE instantiated
        //so that Delphi keeps the RTTI information avaialble
        Result:=Mar.UnMarshal(Value) as TTestObjectList<T>;
      finally
        Mar.Free;
      end;
    end;


    initialization
      //force delphi RTTI into maintaining the Generic class information in memory
      x:=TTestObjectList<TTestObject>.Create;

    finalization
      X.Free;

    end.

Существует нескольковещи, которые важно отметить: если универсальный класс создается во время выполнения, информация RTTI НЕ сохраняется, если в памяти нет глобально доступной ссылки на этот класс.См. Здесь: Delphi: RTTI и TObjectList

Итак, вышеприведенный модуль создает такую ​​переменную и создает ее экземпляр, как описано в связанной статье.

Обновлена ​​основная процедура, которая показывает как маршалинг, так и демаршалинг данных для обоих объектов:

    procedure Main;
    var
      aTestobj,
      bTestObj,
      cTestObj : TTestObject;
      aList,
      bList : TTestObjectList<TTestObject>;
      aJsonObject,
      bJsonObject,
      cJsonObject : TJsonObject;

      s: string;

    begin
      aTestObj := TTestObject.Create(['one','two','three','four']);
      aJsonObject := aTestObj.Marshal;
      s:=aJsonObject.ToString;
      Writeln(s);

      bJsonObject:=TJsonObject.Create;
      bJsonObject.Parse(BytesOf(s),0,length(s));

      bTestObj:=TTestObject.Unmarshal(bJsonObject) as TTestObject;
      writeln(bTestObj.List.Text);

      writeln('TTestObject marshaling complete.');
      readln;

      aList := TTestObjectList<TTestObject>.Create;
      aList.Add(TTestObject.Create(['one','two']));
      aList.Add(TTestObject.Create(['three']));
      aJsonObject := aList.Marshal;
      s:=aJsonObject.ToString;
      Writeln(s);

      cJSonObject:=TJsonObject.Create;
      cJSonObject.Parse(BytesOf(s),0,length(s));
      bList:=TTestObjectList<TTestObject>.Unmarshal(cJSonObject) as TTestObjectList<TTestObject>;
      for cTestObj in bList do
        begin
          writeln(cTestObj.List.Text);
        end;

      writeln('TTestObjectList<TTestObject> marshaling complete.');
      Readln;
    end;
0 голосов
/ 24 октября 2011

Вот мое собственное решение.

Поскольку я очень люблю полиморфизм, я на самом деле также хочу решение, которое может быть встроено в иерархию объектов. Допустим, TTestObject и TTestObjectList - это наш BASE-объект. И с этого мы спускаемся к TMyObject, а также к TMyObjectList. Более того, я внес изменения как в Object, так и в List - добавил свойства для Marshaller / UnMarshaller

TMyObject = class(TTestObject) and TMyObjectList<T:TMyObject> = class(TTestObjectList)

С этим мы теперь вводим некоторые новые проблемы. То есть. как обрабатывать сортировку разных типов между строками в иерархии и как обрабатывать TJsonMarshal и TJsonUnMarshal как свойства в TTestObject и List.

Этого можно избежать, введя два новых метода на уровне TTestObject. Две функции класса, называемые RegisterConverters и RegisterReverters. Затем мы переходим и меняем маршальную функцию TTestObjectList на более простой маршаллинг.

Две функции и свойства класса для объекта и списка.

class procedure RegisterConverters(aClass: TClass; aMar: TJSONMarshal); virtual;
class procedure RegisterReverters(aClass: TClass; aUnMar: TJSONUnMarshal); virtual;

property Mar: TJSONMarshal read FMar write SetMar;
property UnMar: TJSONUnMarshal read FUnMar write SetUnMar;

Функция Маршала List теперь может быть выполнена следующим образом:

function TObjectList<T>.Marshal: TJSONObject;
begin
  if FMar = nil then
    FMar := TJSONMarshal.Create(); // thx. to SilverKnight
  try
    RegisterConverters; // Virtual class method !!!!
    try
      Result := FMar.Marshal(Self) as TJSONObject;
    except
      on e: Exception do
        raise Exception.Create('Marshal Error : ' + e.Message);
    end;
  finally
    ClearMarshal; // FreeAndNil FMar and FUnMar if assigned.
  end;
end;

Конечно, у нас все еще может быть маршаллер для нашего TTestObject, но функция Маршала TTestObjectList НЕ будет его использовать. Таким образом, только ОДИН маршаллер будет создан при вызове маршала TTestObjectList (или потомков). И таким образом мы в конечном итоге получаем только ту информацию, которая нам необходима для воссоздания нашей структуры, когда мы делаем все наоборот - UnMarshalling: -)

Теперь это действительно работает, но мне интересно, есть ли у кого-нибудь комментарии по этому поводу?

Позволяет добавить свойство «TimeOfCreation» в TMyTestObject: свойство TimeOfCreation: TDateTime читает FTimeOfCreation, записывает FTimeOfCreation;

И установить свойство в конструкторе.

FTimeofCreation := now;

И затем нам нужен конвертер, поэтому мы переопределяем виртуальные RegisterConverters TTestObject.

class procedure TMyTestObject.RegisterConverters(aClass: TClass; aMar: TJSONMarshal);
begin
  inherited;  // instanciate marshaller and register TTestObject converters
  aMar.RegisterConverter(aClass, 'FTimeOfCreation',
    function(Data: TObject; Field: String): string
    var
      ctx: TRttiContext;
      date: TDateTime;
    begin
      date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
      Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
    end);
end;

Я получаю очень простой источник, например TTestObject, т.е.

aList := TMyTestObjectList<TMyTestObject>.Create;
      aList.Add(TMyTestObject.Create(['one','two']));
      aList.Add(TMyTestObject.Create(['three']));
      s := (aList.Marshal).ToString;
      Writeln(s);

И теперь мне удалось раскошелиться с полиморфизмом: -)

Это также работает с UnMarshalling, кстати. И я нахожусь в процессе перестройки моего FireBird ORM для создания исходного кода для всех моих объектов, подобных этому.

Текущую старую версию можно найти здесь: http://code.google.com/p/objectgenerator/ Помните, что это работает только для FireBird: -)

...