How to serialize Delphi object

It has been a very lean and easy option of .Net to able serialize/deserialize any serializable object instance.

Only closest option in Delphi is to stream the component using WriteComponent/WriteComponentRes of TStream/TWriter (used for Form storage as DFM, for example). It can be then read back using appropriate counterpart ReadComponent/ReadComponentRes.

Depend on your situation, simply calling .Assign method would work if you are coping data from one instance to another. But it only works between inherited classed which know about each other.

Can we find there something which will allow to pass objects states in more readable format?

There is a very powerful infrastructure available to do full serialization without knowing underlying class structure – RTTI (Run-time Type Information).

All functions we would be looking at are defined in TypInfo.pas unit.

First thing first. To be able to work within RTTI, you need to operate on the object which has published and public properties.

function GetPropList(TypeInfo: PTypeInfo;  out APropList: PPropList): integer;

A function will return number and reference to the list (array) of properties published (public and published) by the class (VMT information). List will also include published methods. Simply walking through it will give you an access to property/method information.

In our example we are not interested in the published methods, so lets filter it out:

var
  i: integer;
  lPropInfo: PPropInfo;
  lPropCount: integer;
  lPropList: PPropList;
  lPropType: PPTypeInfo;
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;
  // ... processing of the properties ...
  end;
end;

What other Kind of information is present in the list? Bellow is full definition of the type

type
  TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration,
tkFloat, tkString, tkSet, tkClass, tkMethod, tkWChar,
tkLString, tkWString, tkVariant, tkArray, tkRecord,
tkInterface, tkInt64, tkDynArray);

Now you are ready to get or set the value of the property:

function GetPropValue(Instance: TObject; const PropName: string;
  PreferStrings: Boolean = True): Variant;
procedure SetPropValue(Instance: TObject; const PropName: string;
  const Value: Variant);

And now you are ready to serialize your object, store it, load definition and deserialize an object back. And it could be not necessarily the same object.
You would have to loop through the properties, read the values, and store it as an XML for later use.

I am not going to go through all specific details in this post, instead, you can find a full code for XML Class serializer here.

This entry was posted in Delphi and tagged , , , , , , , , , . Bookmark the permalink.

18 Responses to How to serialize Delphi object

  1. lwgboy says:

    Your Delphi VCL Component is very useful!
    You’re a gret man!
    Thank you very much!

  2. Will says:

    A very useful article.

    Downloading the code, I couldn’t get the SerilializeObjectToXML to work without amending the code slightly:

    see the changed line comment.

    if aNodeName='' then
        begin
          if (aInstance is TComponent) and ((aInstance as TComponent).Name'') then
            lName := (aInstance as TComponent).Name
          else
    //***** Changed line *****
            lName := (aInstance as TObject).ClassName;
    //***** Changed line *****
        end
        else
          lName := aNodeName;

    Am I using this incorrectly ?

    Regards
    Will

  3. Not exactly.

    • Serialized object has to have a name to be uniquely identified.
    • Class name is not unique Id for identification.
    • RTTI would not work properly with simple TObject, You would have to pass at least TPersistent descendant
    • Otherwise, you would not use the function but create TXMLSerializer instance, use SaveObject/LoadObject and supply aNodename
  4. stoxx says:

    Very good component !
    But there is a small error in your component in procedure “RemoveExistingInfo”.
    You mean:

    procedure RemoveExistingInfo(aObjectName: string; aRootNode: IXMLNode);
      // remove class definition from existing document
      var
        lNode: IXMLNode;
      begin
        if aResetXML then
        begin
          aRootNode.ChildNodes.Clear;
        end
        else
        begin
          if aObjectName'' then
          begin
            lNode := Self.FindNode(aObjectName);
    //        lNode := aRootNode.ChildNodes.FindNode(aObjectName);
            if Assigned(lNode) and (lNode.Attributes['classname'] = aInstance.ClassName) then
              aRootNode.ChildNodes.Remove(lNode);
          end;
        end;
      end;
  5. stoxx says:

    Hello,
    the component has one failure.

    when you save a TStringgrid

    var sg : TStringgrid;
    begin
     SaveToXML(sg);
     sg.defaultrowheight := 30;
     LoadFromXML(sg);
    end;

    the stringgrid do not restore ..

  6. Stoxx,
    First issue raised was a bug and is addressed.
    About the second issue, this is by design, since default values were not stored for the component. I have extended StorageOptions to include a new parameter – soStoreDefaultPropValues – which indicates that default values should be stored as well.
    I will be uploading a new version tonight.

    Thank you

  7. stoxx says:

    The parameter – soStoreDefaultPropValues – is very good.

    First issue: I have added this function to your code now.

    function TXMLSerializer.FindNodeByName(const aName: string; aRootNode: IXMLNode): IXMLNode;
      function lfFindNode(aRootNode: IXMLNode): IXMLNode;
      var
        i: Integer;
        lNode: IXMLNode;
      begin
        for i := 0 to aRootNode.ChildNodes.Count - 1 do
        begin
          lNode := aRootNode.ChildNodes[i];
          if (lNode.NodeName = 'class') and (lNode.Attributes['name']=aName) then
          begin
            Result := lNode;
            Break;
          end;
          Result := lfFindNode(lNode);
          if Assigned(Result) then
            Break;
        end;
      end;
    begin
      Result := lfFindNode(aRootNode);
    end;

    Change in RemoveExistingInfo:

    lNode := Self.FindNodeByName(aObjectName, aRootNode);

    Are you interested to work together? I have developed some new features for your component..

  8. Stoxx,
    thank you for your suggestions.
    I have got a few minutes to spare and added a simple support for default properties.
    Sorry, I have changed originally proposed set value to soResetDefaultValues since it is now a singleton operation performed in the beginning of the deserialization process.
    Updated code is available for download from original location.
    Some other code changes are introduced.

    Logic described:
    When enabled, it will force a component to reset to default values for Integer, Char, Set and Enumeration properties.
    There is still a possibility of explicit “stored” logic being used for other types and/or default being set by other means. In this case we would be forced to come up with other way to reset those values in the code before deserialization is invoked.

    And, off course, I am open to any further improvements and suggestions for the component.

  9. stoxx says:

    Thank you for updating the  component!

    Is it possible not restore all default values, but only  the not saved values?
    In your case there were bad optical effects, because of two restoration.

  10. Yes, it is possible. It will require a two parse loops though. I’ll see how to make it less intense.
    On another hand many controls has BeginUpdate/EndUpdate methods which allow prevent unnecessary redraw until operation is completed. It may be useful here.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

  • About

    From being a junior developer all the way to Development Manager position, I was always interested in new technologies. Passionate speaker, IT junky, developer, architect, team lead, and development manager - many hats, one goal - making software better and closer to people’s needs. For the most part I am using my blog as a scratch pad, writing small articles on things which I came across, was asked about more then once, and which would otherwise require additional research again and again.

    View Serguei Dosyukov's profile on LinkedIn
  • Testimonials

    I’m a huge fan of this theme. I’ve got more than 90,000 pictures in my gallery3 running on a Ubuntu...

    Jklobo

    I've been using this [theme] since the start. It is by far the most attractive, feature packed and stable of...

    Rhyull

    First I’m new to G3. I was wanting to append my forum with a gallery and after doing a lot...

    Setishock
    http://www.animeappeal.com

    more...

  • Categories