Я пытаюсь отобразить 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
Обратите внимание: Это всего лишь первая версия со сценарием солнечного света.