Why can't I get the 'Add' and 'Clear' methode of IList from RTTI

126 Views Asked by At

I have an interface IList<T> (from Spring.Collections) that I want to deserialize with MVCFrameWork. It does not work, because in the code we have a function IsWrap() that looks if the methods Add() and Clear() exist, but the RTTI for IList does not include those methods.

Some code:

TPerson = class (TInterfacedObject)
private
  fLastName: String;
  fFirstName: String;
public
  property FirstName: String read fFirstName write fFirstName;
  property LastName: String read fLastName write fLastName;
end;

implementation

uses
  Generics.Collections,
  Spring.Collections,
  MVCFramework.Serializer.Intf,
  MVCFramework.Serializer.JsonDataObjects;

procedure TSerializerTest.JsonToCollection;
begin
  var strJson: string := '[{"FirstName": "Ricardo", "LastName": "The killer" }, {"FirstName": "Zorro", "LastName": "Z-Man" }]';
  var oPersons : IList<TPerson> := TCollections.CreateObjectList<TPerson> ;
  //  var oPersons : TList<TPerson> := TList<TPerson>.Create ;
  //  var oPersons : TObjectList<TPerson> := TObjectList<TPerson>.Create() ;
  var lSerializer: IMVCSerializer := TMVCJsonDataObjectsSerializer.Create;

  lSerializer.DeserializeCollection(strJson, oPersons, TPerson);

  Assert.AreEqual(oPersons[0].FirstName , 'Ricardo');
  Assert.AreEqual(oPersons[1].FirstName , 'Zorro');
end;

TList<TPerson> and TObjectList<TPerson> work.

IList<TPerson> does not work, because we can not find the methods Add() and Clear() with the RTTI.

procedure TSerializerTest.CheckAddAndClearRTTI;
var
  ObjectType: TRttiType;
  FContext: TRttiContext;
begin 
  var oPersons : IList<TPerson> := TCollections.CreateObjectList<TPerson> ;

  //METHODE ADD AND CLEAR EXIST 
  oPersons.Add(TPerson.Create);  
  oPersons.Clear;

  ObjectType := FContext.GetType(oPersons.AsObject.ClassInfo);
  if (ObjectType.GetMethod('Add') <> nil) and (ObjectType.GetMethod('Clear') <> nil)  then
  begin 
    ShowMessage('Is List');  // DOES NOT GET HERE, WHY
  end;
end;
2

There are 2 best solutions below

0
On

I am assuming that you are using Spring4D 2.0 because in 1.2 the interfaces inherited from IInvokable which gave them method RTTI by default.

However, in 2.0 this has changed and neither the interfaces nor the objects backing them emit any enhanced RTTI simply because the interfaces don't inherit from IInvokable anymore nor have {$M+} added to them, and in the implementing classes all enhanced RTTI is turned off via the $RTTI clause. This is by design and to avoid causing lots of unnecessary binary bloat.

Knowing that one of the major usages of using RTTI on collections is serialization Spring4D offers another approach:

Lists and many other collection types support the non-generic ICollection interface from Spring.Collections which internally creates a wrapper object around the generic collection and exposes functionality from IEnumerable and the Add(const value: TValue) and Clear methods.

var persons := TCollections.CreateObjectList<TPerson>;

var coll := persons as ICollection;
coll.Add(TPerson.Create);
coll.Clear;

I have quickly looked into the DMVC code and I can say that they are handling interfaces in a poor way by simply casting them to object and passing that down. I don't know if they have some extension point for customizing serialization but this needs to be fixed.

1
On

Thanks to Stefan and Remy.

In DMVC I can make a custom serialization

  TListSerializer = class(TInterfacedObject, IMVCTypeSerializer)
  public
    procedure SerializeAttribute(const AElementValue: TValue; const APropertyName: string;const ASerializerObject: TObject; const AAttributes: System.TArray<System.TCustomAttribute>);
    procedure SerializeRoot(const AObject: TObject; out ASerializerObject: TObject;const AAttributes: System.TArray<System.TCustomAttribute>;const ASerializationAction: TMVCSerializationAction = nil);
    procedure DeserializeAttribute(var AElementValue: TValue; const APropertyName: string; const ASerializerObject: TObject; const AAttributes: System.TArray<System.TCustomAttribute>);
    procedure DeserializeRoot(const ASerializerObject: TObject; const AObject: TObject;const AAttributes: System.TArray<System.TCustomAttribute>);
  end;

And I registered to Serializer

  var lSerializer: IMVCSerializer := TMVCJsonDataObjectsSerializer.Create;
  lSerializer.RegisterTypeSerializer(TypeInfo(TFoldedList<System.TObject>), TListSerializer.Create);

IList<TPerson> is a TFoldedList<System.TObject>