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 = class
end;
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 part
Is 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 RegisterConverter
s 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.