I'm currently in the process of clearing out the final parts of this question here.Am I going about this the wrong way / could it be done smarter (interface perhaps) - or am I just plain silly, trying to keep my polymorphism hierarchy alive at all means?I have a TBaseObject class - with a TBaseList<T:TBaseObject> class in the same Unit. And I'm using inheritance to re-use a lot of functionality/properties:Base Unit;TBaseObject = classend;TBaseList<T:TBaseObject> = class(TObjectList<T>)end;CustomCustomer Unit;TCustomCustomer = class(TBaseObject) end;TCustomCustomerList<T:TCustomCustomer> = class(TBaseList<T>)end;Customer Unit;TCustomer = class(TCustomCustomer)end;TCustomerList<T:TCustomer> = class(TCustomCustomerList<T>)end;I'm going for a solution where every single class in my hierarchy can be marshalled into a JSON string notation.Having a look at my TBaseObject, I thought I'll use a property Mar:TJsonMarshal and a class procedure TBaseObject.RegisterConverters(aClass:TClass; aMar:TJsonMarshal); to aid me in the process. This is also working as long as we don't get too creative. Let me explain with a bit more source code.The BaseObject RegisterConverter procedure is somewhat like this:class procedure TBaseObject.RegisterConverters(aClass:TClass; aMar:TJsonMarshal); virtual;begin  aMar.RegisterConverter(aClass, 'fMar',    function(Data: TObject; Field: String): TObject    begin      Result := nil;    end);  end;In a subclass - lets look at TCustomer I will now override the RegisterConverters procedure like this.class procedure TCustomer.RegisterConverters(aClass:TClass; aMar:TJsonMarshal); override;begin  inherited;   aMar.RegisterConverter(aClass, 'fTimeOfLastContact',  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;Now we place the central marshalling in TBaseObject like this :function TBaseList<T>.Marshal: TJSONObject;begin  fMar := TJSONMarshal.Create();  try    RegisterConverters;    try      Result := fMar.Marshal(Self) as TJSONObject;    except      on e: Exception do        raise Exception.Create('Marshal Error : ' + e.Message);    end;  finally    fMar.Free;  end;end;By doing this I have very easy access to do complete marshalling of my class TCustomer.Simply by declaring a TJsonObject variable and do this:SomeJsonObj := SomeCustomer.Marshal;And every other subclass (I have a lot) can do the same - just remember to override the RegisterConverter procedure in the subclass to support special types.But is this really the best solution?Marshalling the List partIs this an OK way of doing it?Remember - it actually works. And REALLY eases the process of writing new units base on TBaseObject to achieve marshalling.I always implement a List class to go alongside with my object class.Hence I have TBaseObject = class and TBaseList<T:TBaseObject> = TObjectList<T> declared in my base unit.Now with the nice feature of being able to marshal any instance (ie. TCustomer) I would also like the possibility of marshaling a TCustomerList<TCustomer> class.What I came up with was:Make a copy of the RegisterConverters and Marshal method from the BaseObject implementation and use it in the List. That way - I should be able to get started.I end up with a RegisterConverters procedure like this for the List:procedure TBaseList<T>.RegisterConverters(aClass:TClass; aMar:TJsonMarshal); virtual;begin  aMar.RegisterConverter(aClass, 'fMar',    function(Data: TObject; Field: String): TObject    begin      Result := nil;    end);    T.RegisterConverters(T, fMar); // class method - Register element specific converters.end;Note that the List version of the RegisterConverters is NOT a class procedure. I do not see the need (but that might be my problem later). But now it should be clear as to WHY it was declared as a class method on the BaseObject class.Implementing a TCustomerList<T>.RegisterConverters is easy as pie. Let's say we have a property DataBuildTime : TDateTime; on the list. Then again we would need a converter to ensure our datetime format is maintained:procedure TCustomerList<T>.RegisterConverters(aClass:TClass; aMar:TJsonMarshal); override;begin  inherited;   aMar.RegisterConverter(aClass, 'fDataBuildTime',  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;And finally we can do this with a TCustomerList<TCustomer> variable called aList:SomeJsonObject := aList.Marshal;The problem:This still works - my problem first arises when combining my hierarchy classes.Having a TWorker that holds a TCustomerList<TCustomer>, then my whole marshaling act falls apart.
Is Marshalling converters/reverters via polymorphism realistic?
delphi
Your RegisterConverters functions are bad, and part of the reason is that they're not sure what they're registering anything for. The class methods actually receive two classes as arguments. The first is the obvious one in aClass, but then there's also Self. Ideally, aClass should be a descendant of Self, so you should assert that:Assert(aClass.InheritsFrom(Self),  'I''m being asked to register things for an unrelated class');Once you've determined that the assertion never fails, you can simplify your code by removing the aClass parameter entirely. That way, it's clear that the RegisterConverters method is to register converters that the class needs for its own marshaling. For example, TCustomer has an fTimeOfLastContact field, so when the program calls TCustomer.RegisterConverters, it should register a converter for that field on TCustomer classes. And since we already know that the Data argument will be an instance of TCustomer, we don't really need all the RTTI exercises, either. Just type-cast to the type you already know it is, and read the fTimeOfLastContact field directly.class procedure TCustomer.RegisterConverters(aMar: TJsonMarshal); override;begin  inherited;   aMar.RegisterConverter(aClass, 'fTimeOfLastContact',    function(Data: TObject; Field: string): string    var      date: TDateTime;    begin      Assert(Data is TCustomer);      Assert(Field = 'fTimeOfLastContact');      date := TCustomer(Data).fTimeOfLastContact;      Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);    end);  end;The RegisterConverters methods for your list classes should be class methods. You say you don't see the need for them to be class methods, but if that's the case, then you're looking at things backward. Every method should be a class method unless it requires access to instance data. Your lists' RegisterConverters methods never access any instance data. In particular, they never consider their own lengths or their contents. They're designed to teach the marshaller how to marshal an arbitrary instance of the list class. Your example tells the marshaller that instances of TCustomerList<T> should have a JSON property named fDataBuildTime, and when it's time to marshal a list, the marshaller will call the callback function to ask what value that property should have for the given instance. None of that depends on the instance you call RegisterConverters on.class procedure TCustomerList<T>.RegisterConverters(aMar: TJsonMarshal); override;begin  inherited;  aMar.RegisterConverter(Self, 'fDataBuildTime',    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);  T.RegisterConverters(aMar);end;I've added a call to T.RegisterConverters at the end so that when you register the list class, you implicitly register the contents, too.I see no reason for fMar to be a member of your TBaseObject class. It being a member suggests that every single one of your objects will have its own JSON marshaller, which is ludicrous, especially since you've demonstrated how that field is used in practice. You create the marshaller, use it, and destroy it all in the same Marshal method. Here's how your Marshal method should look instead:function TBaseList<T>.Marshal: TJSONObject;var  Mar: TJSONMarshal;begin  Mar := TJSONMarshal.Create;  try    RegisterConverters(Mar);    try      Result := Mar.Marshal(Self) as TJSONObject;    except      on e: Exception do        raise Exception.Create('Marshal Error : ' + e.Message);    end;  finally    Mar.Free;  end;end;