JSON к объекту. Проблема со списками Generi c - PullRequest
3 голосов
/ 09 февраля 2020

Я пытаюсь отобразить JSON на структуру объекта. Сначала какой-то код

Мой Delphi DTO:

type
  TElement = class
  private
    FItem: string;
  public
    property Item: string read FItem write FItem;
  end;

  TJsonTest = class
  private
    FItems: TObjectList<TElement>;
  public
    constructor Create;
    destructor Destroy; override;
    property Items: TObjectList<TElement> read FItems;
  end;

constructor TJsonTest.Create;
begin
  inherited;
  FItems := TObjectList<TElement>.Create;
end;

destructor TJsonTest.Destroy;
begin
  FItems.Free;
  inherited;
end;

Вот JSON Я хочу отобразить на вышеуказанную структуру

{ 
   "items":[ 
      { 
         "item":"Item"
      }
   ]
}

Тогда проблема :

Следующая строка вызывает исключение, потому что синтаксический анализатор ожидает массив, а не список.

jsonTest := TJson.JsonToObject<TJsonTest>('{"items":[{"item":"Item"}]}');

Чтобы преодолеть эту проблему, я попытался создать Перехватчик

type
  TGenericListFieldInterceptor = class(TJSONInterceptor)
  public
    procedure ObjectsReverter(Data: TObject; Field: string; Args: TListOfObjects); override;
    function ObjectsConverter(Data: TObject; Field: string): TListOfObjects; override;
  end;

{ TGenericListFieldInterceptor }

function TGenericListFieldInterceptor.ObjectsConverter(Data: TObject; Field: string): TListOfObjects;
var
  ctx: TRttiContext;
  list: TList<TObject>;
begin
  list := TList<TObject>(ctx.GetType(Data.ClassInfo).GetField(Field).GetValue(Data).AsObject);
  Result := TListOfObjects(list.List);
  SetLength(Result, list.Count);
end;

procedure TGenericListFieldInterceptor.ObjectsReverter(Data: TObject; Field: string; Args: TListOfObjects);
var
  ctx: TRttiContext;
  list: TList<TObject>;
  ListOfObjects : TListOfObjects;
begin
  list := TList<TObject>(ctx.GetType(Data.ClassInfo).GetField(Field).GetValue(Data).AsObject);
   //<-- Problem: List is nil 
end;

И затем из-за изменения моего DTO:

  TJsonTest = class
  private
    [JSONReflect(ctObjects, rtObjects, TGenericListFieldInterceptor, nil, true)]
    [JSONOwned(false)]
    FItems: TObjectList<TElement>;
  public
    constructor Create;
    destructor Destroy; override;
    property Items: TObjectList<TElement> read FItems;
  end;

Моя проблема в TGenericListFieldInterceptor.ObjectsReverter при попытке получить список (FItems), в который я должен добавить элементы, это nil!

Короче говоря: TGenericListFieldInterceptor.ObjectsReverter как мне получить экземпляр объекта FItems?

Я использую Delphi 10.3.3

** ОБНОВЛЕНИЕ **

После некоторой отладки и поиска в решении @ UweRaabe я нашел проблему.

Первое, где я забыл использовать REST. Json .Типы, чтобы JSONOwned не вызывался, и там мой объект был освобожден, и это дало мне мой список!

Но следующая проблема где немного сложнее взломать: у меня теперь есть мой список, но Args в TGenericListFieldInterceptor.ObjectsReverter, где всегда ноль . Через некоторое время я узнал, что это было вызвано тем, что ObjectType на моем TJSONUnMarshal потомке, где nil ! Как бы я ни старался, я не смог инициализировать его в экземпляре, который использовался внутри TJSONUnMarshal.

После еще одной отладки я обнаружил проблему: `TJSONUnMarshal.FieldReverter. Это метод, который отвечает за создание экземпляра моего атрибута JsonReflect.

Вот первая часть метода:

function TJSONUnMarshal.FieldReverter(Field: TRttiField): TJSONInterceptor;
var
  fieldAttr: TCustomAttribute;
begin
  Result := nil;
  if Field <> nil then
  begin
    // First check for individual Attribute
    try
      for fieldAttr in Field.GetAttributes do
        if fieldAttr is JsonReflectAttribute then
          Result := JsonReflectAttribute(fieldAttr).JSONInterceptor;     
   ..
      end;
    except

    end;
  end;
end;

JsonReflectAttribute(fieldAttr).JSONInterceptor creates the instance but due to the typecast  `JsonReflectAttribute(fieldAttr)` it is allways on the original `JsonReflectAttribute` class  not on my descendant `TGenericListFieldInterceptor`. Form here I see two ways ahead:

1) Редактирование в исходном блоке (REST.JsonReflect). Это не вариант в моем Мире!

2) Перенаправление кода!

Поэтому я выбираю вариант 2.

В общем, я создал новую версию TJSONUnMarshal.FieldReverter и в во время выполнения заменил исходный на мой.

В новой версии я установил свойство ObjectType моего TJSONInterceptor. Поскольку я знаю, что этот атрибут размещен на потомке TObjectList, я могу получить класс из метода добавления:

function TJSONUnMarshalFix.FieldReverter(Field: TRttiField): TJSONInterceptor;
var
  FieldAttr: TCustomAttribute;
  RttiType: TRttiType;
  RttiMethod: TRttiMethod;
  RttiParameter: TRttiParameter;
begin
  Result := nil;

  if Field = nil then
    Exit;

  try
    for FieldAttr in Field.GetAttributes do
      if FieldAttr is JsonReflectAttribute then
      begin
        Result := JsonReflectAttribute(FieldAttr).JSONInterceptor;

        RttiType := Field.FieldType;
        RttiMethod := RttiType.GetMethod('Add');

        if RttiMethod <> nil then
          for RttiParameter in RttiMethod.GetParameters do
            if RttiParameter.Name = 'Value' then
              if RttiParameter.ParamType.IsInstance then
                Result.ObjectType := RttiParameter.ParamType.AsInstance.MetaclassType;
      end;

  except

  end;
end;

Я создал небольшую демонстрацию, связывающую все это вместе http://borrisholt.dk/Stackoverflow/GenericListReflect.7z

Обратите внимание: Это всего лишь первая версия со сценарием солнечного света.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...