Thursday, October 8, 2009

Xml Serialization - Basic Usage

This post relates to XmlSerial.pas which provides XML serialization and de-serialization using the new RTTI in Delphi 2010.

This class has been built to serialize any public and published property or Field.
This was done to mimic the behavior found in the .NET Xml Serialization with the goal of having a set of objects that can serialize in .NET using Delphi Prism and Win32 using Delphi 2010. The complete goals of what I want to accomplish with XmlSerial.pas look at the source code, I detailed out what still needs to be done.

There are two ways to work with the Xml Serialization, one depends on a Pointer to the Type Information, the other uses Generics to get it form the type specified as a parameter.

// Method 1:
var
o : TypeIWantToSerailze;
s : TXmlTypeSerializer;
x : TXmlDocument;
v : TValue;
begin
s := TXmlTypeSerializer.create(TypeInfo(o));
x := TXmlDocument.Create(Self); // NEVER PASS NIL!!!
s.Serialize(x,o);
x.SaveToFile('FileName.txt');
v := s.Deserialize(x);
o := v.AsType<TypeIWantToSerailze>;
x.free;
s.free;
end;

// Method 2:
var
o : TypeIWantToSerailze;
s : TXmlSerializer<TypeIWantToSerailze>;
x : TXmlDocument;
begin
s := TXmlTypeSerializer<TypeIWantToSerailze>.create;
x := TXmlDocument.Create(Self); // NEVER PASS NIL!!!
s.Serialize(x,o);
x.SaveToFile('FileName.txt');
o := s.Deserialize(x);
x.free;
s.free;
end;

And here is the full code showing how to do this using the generic version.

unit uPerson;
interface

type
TPerson = class(TObject)
private
FLastName: String;
FBirthday: TDateTime;
FMiddleName: String;
FFirstName: String;
function GetFullName: String;
published
public
property FirstName : String read FFirstName write FFirstName;
property LastName : String read FLastName write FLastName;
property MiddleName : String read FMiddleName write FMiddleName;
property FullName : String read GetFullName;
property Birthday : TDateTime read FBirthday write FBirthday;
procedure Save(FileName : String);
class function Load(FileName : String) : TPerson;
end;


implementation
uses
XmlDoc,
Classes,
XmlSerial;

{ TPerson }

function TPerson.GetFullName: String;
begin
result := FFirstName + ' ' + FMiddleName + ' ' + FLastName;
end;

class function TPerson.Load(FileName: String): TPerson;
var
lSerialize : TXmlSerializer<TPerson>;
lOwner : TComponent;
lDoc : TxmlDocument;
begin
lOwner := TComponent.Create(nil); // Required to make TXmlDocument work!
try
lDoc := TXmlDocument.Create(lOwner); // will be freed with lOwner.Free
lDoc.LoadFromFile(FileName);
lSerialize := TXmlSerializer<TPerson>.Create;
try
result := lSerialize.Deserialize(lDoc);
finally
lSerialize.Free;
end;
finally
lOwner.Free;
end;
end;

procedure TPerson.Save(FileName: String);
var
lSerialize : TXmlSerializer<TPerson>;
lOwner : TComponent;
lDoc : TxmlDocument;
begin
lOwner := TComponent.Create(nil); // Required to make TXmlDocument work!
try
lDoc := TXmlDocument.Create(lOwner); // will be freed with lOwner.Free
lSerialize := TXmlSerializer<TPerson>.Create;
try
lSerialize.Serialize(lDoc,Self);
lDoc.SaveToFile(FileName);
finally
lSerialize.Free;
end;
finally
lOwner.Free;
end;
end;

end.

The project that shows how to use this object.

program Project12;

{$APPTYPE CONSOLE}

uses
SysUtils,
Windows,
XmlSerial,
Forms,
uPerson in 'uPerson.pas';

var
P : TPerson;

begin
Application.Initialize;
P := TPerson.Create;
try
P.FirstName := 'John';
P.MiddleName := 'C';
P.LastName := 'Doe';
P.Birthday := EncodeDate(1995,2,14);
P.Save('C:\test.xml');
finally
P.Free;
end;

p := TPerson.Load('C:\test.xml');
try
writeln(P.FullName);
Writeln(DateToStr(P.Birthday));
Readln;
finally
p.Free;
end;
end.

Output:

John C Doe
2/14/1995

Current output of the XML file, notice the Date, it's something I want to change, if you check the roadmap in the xmlserial.pas source.


<TPerson>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<MiddleName>C</MiddleName>
<Birthday>34744</Birthday>
</TPerson>


Hopefully this covers enough of how to use this to get you started.
I will show how to customize the behavior using attributes in a future post.

RTTI Article List

7 comments:

  1. Hi Robert, your articles are very nice.
    But why you haven't used TConverter<TSerial> ?

    ReplyDelete
  2. Because I did not know it existed.

    It is intresting and deserves further look, but right now looking at it with only a few minutes to review, I am not sure I would want to use it.

    ReplyDelete
  3. I have now looked with a bit more detail at TConvert<TSerial>

    It designed to serialize all of the fields only,
    which works for most serialization. Would might be great thing to look at for a binary serializer.

    But, when you want to mimic what .NET provides to provide a common code base, you need to work with just the public and published fields and properties.

    Although, I might be mistaken it also does not appear have an elegant way I could control if the serialization names and locations like I do with Attributes.

    http://robstechcorner.blogspot.com/2009/10/xml-serialization-control-via.html

    ReplyDelete
  4. Really cool. Is your serializer serializing only stuff with the [TSerialializable] attribute or all properties?

    ReplyDelete
  5. It's handles all properties and fields in public and published, unless you add the
    [XmlIgnore]

    ReplyDelete
  6. Is there any way to have a data structure that results in a parent-child xml, in .net I used an array inside the class to have that. I want to have an structure that results in something like this
    <people>
    <count>2</count>
    <person>
    <name>Person1</name>
    </person>
    <person>
    <name>Person2</name>
    </person>
    </people>

    Tnx

    ReplyDelete
  7. Why do you use TXMLDocument instead of IXMLDocument?
    Because IXMLDocument not require TComponent to make TXmlDocument work!

    ReplyDelete