I am trying to register a custom converter to avoid the inherited properties in TObjectList
– mainly FListHelper
and FOwnsObjects
. But I cannot get the custom converter to register, and the documentation shows no real examples.
(No this is not a duplicate of: How to hide "ownsObjects" and "listHelper" TObjectList's properties from a Json using Delphi (Rest.JSON)?)
I am trying to get a converter Registered – but it seems to never run.
I have wrapped it in a custom class of mine that looks like this:
TMyJsonConverter = class(TJsonConverter)
class function JsonConvert(ObjectToConvert:TObject): string;
private
type
TListOfObjectInterceptor = class(TJSONInterceptor)
function ObjectsConverter(Data: TObject; Field:string): TListOfObjects; override;
end;
end;
function TMyJsonConverter.TListOfObjectInterceptor.ObjectsConverter(Data: TObject; Field:string): TListOfObjects;
begin
raise Exception.Create('converter found');
end;
class function TMyJsonConverter.JsonConvert(ObjectToConvert: TObject): string;
begin
var customConverter := TMyJsonConverter.TListOfObjectInterceptor.Create();
var otherConverter := TMyJsonConverter.Create;
var marshaller := TJSONMarshal.Create(otherconverter);
marshaller.RegisterConverter(
ObjectToConvert.ClassType,
'*',
customConverter.ObjectsConverter
);
var json := marshaller.Marshal(ObjectToConvert);
try
exit(json.ToString);
finally
marshaller.Free;
end;
end;
I have tried to register the types of TObjectConverter
, TObjectsConverter
, TTypeObjectsConverter
but I never seem to get into the conversion function. I can see that the call to register does register the converter, but when I marshal the JSON, it does not find the custom converter again.
Here is a sample structure that highlights the issue, I want to marshal TMySampleDTO
as JSON:
type
TEmployee = class
public
Id: Integer;
Name: string;
end;
TEmployeeList = class(TObjectList<TEmployee>);
TWorktime = class
public
EmployeeId: Integer;
DepartmentId: Integer;
StartTime: TDateTime;
StopTime: TDateTime;
end;
TWorktimeList = class(TObjectList<TWorktime>);
TDepartment = class
public
Id: Integer;
Address: string;
Employees: TEmployeelist;
end;
TDepartmentList = class(TObjectList<TDepartment>);
TMySampleDTO = class
public
Departments: TDepartmentList;
Worktimes: TWorktimeList;
Employees: TEmployeeList;
end;
UPDATE: I got the Converter to run, apparently even though Embarcadero defined the const FIELD_ANY
as '*'
, it doesn’t run if you don’t specify the exact fieldname, in my case FListHelper
. This raises the next issue though, I also have to give the exact type, as it doesn’t check for inheritance. So if my object structure has properties derived from TObjectList<T>
all those lists will be serialized as objects with a list as a property.
2
Answers
Here is something that works - for some reason it doesnt work when the top level object is a list, but I can live with that for now. It uses RTTI and some questionable methods for finding Lists in an object, but it works for me - somewhat.
If
ObjectToConvert
is aTList<T>
descendant, it will still return json as an object with the property listHelper, containing the elements of the list.But only for the top level object
If anyone can figure out why the converter doesn't work for the top list (outermost json value), feel free to tell me why, and I will add it to solution.
The documentation for
TTypeMarshaller.RegisterConverter
lacks any detailed information leaving you no option but to study Delphi RTL source code. At first look we can divide all overloaded versions of the method into 3 groups:TConverterEvent
– registers a converter, which performs conversion based on itsConverterType
property. This property read-only, but its value is set along with setting any of its conversion delegates (*Converter
properties).T...Converter
– registers a converter forFieldName
(2nd parameter) within class typeclazz
(1st parameter). Let’s call it a "field converter".TType...Converter
– registers a converter for class typeclazz
(1st parameter). This basically calls the first overload withFieldName
set to'*'
, which is reserved for type converters.In your sample code you are registering a field converter for field with name
*
within class type of an instance provided as an argument to yourTMyJsonConverter.JsonConvert
method. That explains why your conversion routine is not invoked.Furthermore you create an instance
TListOfObjectInterceptor
in order to pass a reference to itsObjectsConverter
method toTTypeMarshaller.RegisterConverter
method. This way you leak thisTListOfObjectInterceptor
instance. Interceptors should be used in combination withJsonReflect
attribute.The correct way to register a field converter is:
The correct way to register a type converter is:
As you can see, this is as much work as using
JsonReflect
attributes. In theory you could register converters dynamically based on RTTI, but this can be cumbersome due to nested types and inheritance.To sum that up, even though Delphi JSON library provides ways to hook into the marshalling process, it’s not very flexible and supports only very basic scenarios (see also recent question Spring4D Nullable JSON serialization).
The last thing I’d like to point out is that you should pay more attention to conventions. Fields of records/classes should be prefixed with
F
by convention. Delphi JSON library honors that and removes thoseF
‘s in process of serialization. Serializingtype TData = class Foo: string; end;
to JSON yields:{"oo":""}
.