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
Hi Robert, your articles are very nice.
ReplyDeleteBut why you haven't used TConverter<TSerial> ?
Because I did not know it existed.
ReplyDeleteIt 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.
I have now looked with a bit more detail at TConvert<TSerial>
ReplyDeleteIt 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
It's handles all properties and fields in public and published, unless you add the
ReplyDelete[XmlIgnore]
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
ReplyDelete<people>
<count>2</count>
<person>
<name>Person1</name>
</person>
<person>
<name>Person2</name>
</person>
</people>
Tnx
Why do you use TXMLDocument instead of IXMLDocument?
ReplyDeleteBecause IXMLDocument not require TComponent to make TXmlDocument work!
hi,
ReplyDeleteTPerson in new class TInformation , if FInformation is null and lSerialize.Serialize(lDoc, person); error.
How do I solve this problem?
TPerson = Class(TPersistent)
private
FInformation:TInformation
...
The example code does not deal with that concept. You would have to modify the code to deal with nil values.
DeleteTXmlCustomTypeSerializer.SerializeValue change
ReplyDeletemy problem nill class.
I solved:
...
var
Tmpclass:TClass;
isOk:Boolean;
begin
....
if Not (Map.isList) then
begin
for MapItem in Map.List do
begin
Assert(Assigned(MapItem.Member));
isOk:=true;
if (Value.IsClass) then
begin
Tmpclass:=Value.AsClass;
isOk:=Assigned(Tmpclass);
end;
if (isOk) then
begin
CurrentValue := MapItem.Member.GetValue(Value);
SerializeValue(CurrentValue, MapItem, NewNode, Doc);
end;
hi
ReplyDeleteTXmlCustomTypeSerializer.DeSerializeValue change
class is null and before control Child.ChildNodes.Count > 1 add code
function TXmlCustomTypeSerializer.DeSerializeValue(Node: IXMLNode; MapType: TRttiType; Map: TMemberMap): TValue;
var
Children: IXMLNodeList;
Child: IXMLNode;
MapItem: TMemberMap;
ChildValue: TValue;
I: Integer;
ListValue: TValue;
lAdd: TElementAdd;
ListMapItem: TMemberMap;
begin
if MapType.IsInstance then
begin
if Node.ChildNodes.Count > 1 then
Result := MapType.AsInstance.MetaclassType.create;
end
else
begin
// Create an Empty Record, Value Type, etc....
TValue.Make(nil, MapType.Handle, Result);
end;
// structure type with Mapped Members, and it's not a list type
if (MapType is TRttiStructuredType) and (Length(Map.List) > 0) and (Not Map.isList) and (Node.ChildNodes.Count > 1) then
begin
Children := Node.ChildNodes;
for MapItem in Map.List do
begin
case MapItem.NodeType of
ntElement:
Child := Children.FindNode(MapItem.NodeName);
ntAttribute:
begin
if Node.HasAttribute(MapItem.NodeName) then
Child := Node.AttributeNodes.FindNode(MapItem.NodeName)
else
Child := nil;
end;
end; { Case }
if Assigned(Child) then
begin
if (MapItem.Member.MemberType.IsInstance) then
begin
if Child.ChildNodes.Count > 1 then
begin
ChildValue := DeSerializeValue(Child, MapItem.Member.MemberType, MapItem);
if (not ChildValue.isEmpty) then
MapItem.Member.SetValue(Result, ChildValue);
end;
end
else
begin
ChildValue := DeSerializeValue(Child, MapItem.Member.MemberType, MapItem);
if (not ChildValue.isEmpty) then
MapItem.Member.SetValue(Result, ChildValue);
end;
end;
end;
end
else if Map.isList then
begin
Children := Node.ChildNodes;
// Create Correct Element Add Factory
if Children.Count > 1 then
lAdd := TElementAddFactory.CreateElementAdd(Result);
// Loop through items to add.
for I := 0 to Children.Count - 1 do
begin
Child := Children.Nodes[I];
ListMapItem := Map;
ListMapItem.isList := false;
ListMapItem.NodeType := ntElement;
ListValue := DeSerializeValue(Child, FCtx.GetType(lAdd.AddType), ListMapItem);
lAdd.Add(ListValue);
end;
if Children.Count > 1 then
begin
lAdd.AddFinalize;
Result := lAdd.List;
end;
end
else // Not a structure Type or List, convert from String to Value
begin
Result := TextToValue(MapType, Node.Text);
end;
end;