Introduction
You may be already familiar with serializing C++ objects using MFC's CArchive
class. Serialization is the process of storing the object properties and contained objects persistently, so that the object can be reconstructed to its previous state by loading the persistently stored data. If you use CArchive
to serialize, the object will be serialized into a binary form.
In this article I will show you how to serialize/deserialize a class into/from XML string. The object will be serialized into a text form in this case, since we are using XML.
Platform/Technology used: ATL, MSXML
Why Serialize an object into XML?
With the widespread usage and acceptance of XML as the format to exchange data over disparate systems, serializing an object into XML has many advantages:
- XML documents can be easily exchanged over the web
- Can work on disparate platforms
- XML tree can be parsed easily by using readily available XML parsers in the market such as Microsoft XML Parser
Now, Let me show you how to do the XML-izing.
How it is done?
The generic class CXMLArchive
has most of the functionality to XMLize (i.e. serialize) a class and deXMLize (i.e. deserialize) a class. Just derive your class from CXMLArchive
and override the pure virtual methods to provide class specific implementation. Let's take an example and see how it works.
Example
Let's take a simple employee
object and see how it can be serialized into the schema format shown below.
Employee schema
<?xml version="1.0" ?>
<Schema name="Employee-Schema"
xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="Id" dt:type="int"/>
<ElementType name="FirstName" dt:type="string"/>
<ElementType name="LastName" dt:type="string"/>
<ElementType name="Address" dt:type="string"/>
<ElementType name="Employee" model="closed" content="eltOnly">
<element type="Id"/>
<element type="FirstName"/>
<element type="LastName"/>
<element type="Address"/>
</ElementType>
</Schema>
Employee object
In employee
object, do the following things to XMLize it:
In header file
- Include XMLArchive.h file
- Derive employee class from
CXMLArchive
- Call
XML_DECLARE_NODE_VAR_MAP(n)
, where n
is the size of nodevarmap
structure
- Declare the following virtual functions from
CXMLArchive
class
InitXMLNodeVariableMap()
FillAttribute()
Save()
In CPP file
-
Implement the virtual methods
InitXMLNodeVariableMap()
The purpose of this method is to maintain a dictionary of which XML node is related to which object variable, so that the XML node value can be correctly transferred to the appropriate object variable. Pass XML node, data type and pointer to the object variable as parameters to XML_NODE_VAR_MAP
macro. Note that variant data type (VT_xx
) is used to specify data type.
void CEmployee::InitXMLNodeVariableMap()
{
START_XML_NODE_VAR_MAP()
XML_NODE_VAR_MAP(NODE_ID, VT_I4, &m_lId)
XML_NODE_VAR_MAP(NODE_FIRSTNAME, VT_BSTR, &m_szFirstName)
XML_NODE_VAR_MAP(NODE_LASTNAME, VT_BSTR, &m_szLastName)
XML_NODE_VAR_MAP(NODE_ADDRESS, VT_BSTR, &m_szAddress)
END_XML_NODE_VAR_MAP()
}
FillAttribute()
This method helps to initialize variables of the employee object from the XML tree. Note that all the meat is in the base class and we just need call the base class helper method FillAttributeHelper()
here.
BOOL CEmployee::FillAttribute(MSXML::IXMLDOMNode* pXMLNode)
{
ATLASSERT(pXMLNode);
return FillAttributeHelper(pXMLNode,
XML_NODEVARMAP_STRUCT, XML_NODE_VAR_MAP_COUNT);
}
Save()
The purpose of Save()
method is to build XML tree to represent employee
object. Build the XML tree using helper macros like START_XML, START_XML_ELEMENT
etc.
long CEmployee::Save(MSXML::IXMLDOMNode* pCurrentNode)
{
EmptyDOM();
XML_SET_CURRENTNODE(pCurrentNode)
START_XML()
START_XML_ELEMENT(NODE_EMPLOYEE)
XML_ELEMENT_LONG(NODE_ID, m_lId)
XML_ELEMENT_STR(NODE_FIRSTNAME, m_szFirstName)
XML_ELEMENT_STR(NODE_LASTNAME, m_szLastName)
XML_ELEMENT_STR(NODE_ADDRESS, m_szAddress)
END_XML_ELEMENT()
END_XML()
return S_OK;
}
-
XMLize() method
XMLize()
and deXMLize()
are exposed methods in employee
object to serialize and deserialize. Call Save()
method to build XML tree. Once the XML tree is built, it is easy to get the XML string by calling IXMLDOMDocument::get_xml()
method.
-
deXMLize() method
In deXMLize()
method, just call the base class method LoadXML()
, which takes care of exploring each XML node and transferring the values from XML node to appropriate object variable.
Contained objects
So far so good. Will this work if employee
object contains child objects? The answer is a resounding yes. This framework supports contained objects' XMLization too. You just have to implement ReadContainedObject()
in your derived class (employee
in this case). In overridden ReadContainedObject()
method, just check if the node name is of the contained object and if yes call the contained object LoadXMLNode()
method and set bContainedObjectFound
to TRUE
.
Example of contained object
If the employee
object has a one to one association with a Department
object, let's see how this hierarchy of objects can be XMLized using this framework.
Dept object
Dept
object is a simple object with just one property, name
. In Dept
object, implement all the necessary virtual methods:
InitXMLNodeVariableMap()
void CDept::InitXMLNodeVariableMap()
{
START_XML_NODE_VAR_MAP()
XML_NODE_VAR_MAP("Name", VT_BSTR, &m_szName)
END_XML_NODE_VAR_MAP()
}
FillAttribute()
BOOL CDept::FillAttribute(MSXML::IXMLDOMNode* pXMLNode)
{
ATLASSERT(pXMLNode);
return FillAttributeHelper(pXMLNode,
XML_NODEVARMAP_STRUCT, XML_NODE_VAR_MAP_COUNT);
}
Save()
long CDept::Save(MSXML::IXMLDOMNode* pCurrentNode)
{
EmptyDOM();
XML_SET_CURRENTNODE(pCurrentNode)
START_XML()
START_XML_ELEMENT("Dept")
XML_ELEMENT_STR("Name", m_szName)
END_XML_ELEMENT()
END_XML()
return S_OK;
}
XMLizing Dept object
Once all the virtual methods are implemented by Dept
object, the employee
object just has to call the Dept
object's Save()
method when XMLizing and call LoadXMLNode()
method when deXMLizing.
The modified employee
object Save()
method now looks like below:
long CEmployee::Save(MSXML::IXMLDOMNode* pCurrentNode)
{
EmptyDOM();
XML_SET_CURRENTNODE(pCurrentNode)
START_XML()
START_XML_ELEMENT(NODE_EMPLOYEE)
XML_ELEMENT_LONG(NODE_ID, m_lId)
XML_ELEMENT_STR(NODE_FIRSTNAME, m_szFirstName)
XML_ELEMENT_STR(NODE_LASTNAME, m_szLastName)
XML_ELEMENT_STR(NODE_ADDRESS, m_szAddress)
m_pDept->Save(XML_GET_CURRENTNODE);
END_XML_ELEMENT()
END_XML()
return S_OK;
}
ReadContainedObject()
checks if the node name is Dept
and if yes calls Dept
object's LoadXMLNode()
method to deXMLize Dept
object.
long CEmployee::ReadContainedObject(MSXML::IXMLDOMNode* pNode,
BOOL& bContainedObjectFound)
{
ATLASSERT(pNode);
bContainedObjectFound = FALSE;
BSTR szTempNodeName;
pNode->get_nodeName(&szTempNodeName);
_bstr_t szNodeName(szTempNodeName, FALSE);
if ( szNodeName == _bstr_t("Dept") )
{
bContainedObjectFound = TRUE;
return m_pDept->LoadXMLNode(pNode);
}
return S_OK;
}
You may wonder what is m_pDept
and where it is created?. It is declared in employee.h as:
CComObject<CDEPT>* m_pDept;
m_Dept
is created in the employee
constructor by the code below:
HRESULT hRes = CComObject<CDEPT>::CreateInstance(&m_pDept);
ATLASSERT(SUCCEEDED(hRes));
m_pDept->AddRef();