Recently I needed to serialize objects in Delphi so that I could transfer them across COM. Simple right? In .Net you simply use XmlSerializer, surely there is an equivalent in Delphi? Wrong! Let me just state that I am working in Delphi2005, so there may be an XML serializer in Delphi XE.
After looking around for a third party serializer I could not find something that I could get going easily and that worked the way I expected it to. So I decided to give XML serialization a go and what I could come up with.
Delphi has some fairly good RTTI, and I found the following article helpful: http://blog.dragonsoft.us/2008/04/21/how-to-serialize-delphi-object/. The below implementation is based on this. Also note that only object properties that are Public and Published can be traversed. Something else which I found, was that the objects should inherit from TPersistent.
The heart of this unit is the ‘Parse’ method. This was a very quick implementation so theres a lot that can be added, for example this can be extended to serialize arrays. What this serializer will handle is nested classes.
unit XMLSerializer;
interface
uses
XmlIntf, XMLDoc, TypInfo, windows, sysUtils, forms;
type
TXMLSerializer = class
protected
FXMLDoc : TXMLDocument;
FCurrentNode : IXMLNode;
FPreviousNode : IXMLNode;
procedure Parse(AObject : TObject);
public
procedure Serialize(AObject : TObject;const RootName : string);
//procedure WriteTofile(filename : string);
procedure WriteToStr(var theString : WideString);
destructor Destroy;override;
end;
implementation
procedure TXMLSerializer.Parse(AObject : Tobject);
var
i: integer;
lPropInfo: PPropInfo;
lPropCount: integer;
lPropList: PPropList;
lPropType: PPTypeInfo;
//info : PTypeInfo;
SubObject : TObject;
begin
lpropCount := GetPropList(PTypeInfo(AObject.ClassInfo), lPropList);
for i := 0 to lPropCount - 1 do
begin
lPropInfo := lPropList^[i];
lPropType := lPropInfo^.PropType;
if lPropType^.Kind = tkMethod then
Continue
else if lPropType^.Kind = tkClass then
begin
SubObject := GetObjectProp(AObject, lPropInfo.Name);
FCurrentNode.AddChild(lPropInfo.Name);
FPreviousNode := FCurrentNode;
FCurrentNode := FCurrentNode.ChildNodes.FindNode(lPropInfo.Name);
Parse(SubObject);
FCurrentNode := FPreviousNode;
end
else FCurrentNode.AddChild(lPropInfo.Name).Text := GetPropValue(AObject, lPropInfo.Name, True);
//To extend this check for different lPropType^.Kind
//This could for example be extended to serialize arrays by checking lPropType^.Kind = tkArray
end;
end;
procedure TXMLSerializer.Serialize(AObject : TObject;const RootName : string);
begin
FXMLDoc := TXMLDocument.Create(Application);
FXMLDoc.Active := True;
FXMLDoc.AddChild(RootName);
FCurrentNode := FXMLDoc.DocumentElement;
//We should do some checking here that the object passed in is of type
//TPersistent
Parse(AObject);
end;
procedure TXMLSerializer.WriteToStr(var theString : WideString);
begin
FXMLDoc.SaveToXML(theString);
end;
destructor TXMLSerializer.Destroy;
begin
FXMlDoc.Free;
inherited;
end;
end.
Here is an example of a class that is to be serialized:
TPerson = class(TPersistent)
protected
FName : string;
FAge : Integer;
FEmail : string;
published
property Name : string read FName write FName;
property Age : Integer read FAge write FAge;
property Email : string read FEmail write FEmail;
end;
The resulting XML is as follows:
<Person>
<Name>Bob Sinclair</Name>
<Age>23</Age>
<Email>bob@bobsinclair.com</Email>
</Person>
Typcial usage would be as follows:
procedure TForm1.Button1Click(Sender: TObject);
var
s : TXMLSerializer;
person : TPerson;
begin
//Create the serializer
s := TXMlSerializer.Create();
//instantiate the person object and populate
person := TPerson.Create();
person.Name := 'Bob Sinclair';
person.Age := 23;
person.Email := 'bob@bobsinclair.com';
//unfortunately you have to specify the root tag of the xml in this case 'Person'
s.Serialize(person,'Person');
s.WriteToStr(output);
showMessage(output);
end;