<html>
<head>
<title>Yet Another XML Serialization Library for the .NET Framework</title>
</head>
<body>
<h2>Introduction</h2>
<p> In this article an XML Serialization library is presented which is referred to hereafter
as YAXLib. .NET framework has several serialization capabilities, specially
<a title="MSDN: XmlSerializer class" target="_blank" href="http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx">
XmlSerializer</a> which is widely used by developers to serialize objects to
XML and deserialize later. My problems with the <code>XmlSerializer</code> class of the .NET
framework were: </p>
<ul>
<li>The developer is not free to choose the structure of the generated XML</li><li>
It does not support serializing some collection classes (e.g. <code>Dictionary<,></code>)
or properties of type <code>IEnumerable<></code></li>
<li>When deserializing it fails when some field is not present, making it unsuitable for
storing config files, which might be edited later by human users.<br />
</li>
</ul>
<h2>Why Use YAXLib</h2>
<p>The features of YAXLib that relief problems above, are:</p>
<ul>
<li>The user can decide on the structure of the XML file. A property can be a child
element or attribute for another property or element that has no corresponding
property in the class.</li>
<li>It supports serializing most of the collection types (including arrays, <code>List<></code>, <code>
HashSet<></code>, <code>Dictionary<,></code>, properties of type
<code>IEnumerable<></code> and many others). For more information see the section: <a href="#issues">Known Issues / Shortcomings</a>
(of course the <code>XmlSerializer</code> class also supports many of these collection types).</li>
<li>When it comes to deserialization, the developer can choose what to happen when
the data related to some property is not present in the XML file. This situation
can be regarded as error, so the library throws some exception or logs it as
error; or it can be regarded as warning and some predefined default value
specified by the developer is assigned to the absent property.</li>
<li>The developer can choose the error-handling policy. For data sensitive
applications, the developer can choose that on any exceptional situation the
library should throw and log exceptions. For other situations (e.g. storing
config files in a flexible way), the developer can choose to treat exceptional
situations as warnings, and only log them and let the rest of the process go on.</li>
</ul>
<h2>A Tutorial of YAXLib Usage</h2>
<p>
Let's provide a very simple example and then incrementally improve it to reflect
different aspects of YAXLib. To start, we define a simple <code>Warehouse</code> class:</p>
<pre lang="cs">public class Warehouse
{
public string Name { get; set; }
public string Address { get; set; }
public double Area { get; set; }
}
</pre>
<p>
The following code snippet illustrates what is needed to serialize an object of
the <code>Warehouse</code> class. </p>
<pre lang="cs">// First create an instance of the Warehouse class
Warehouse w = new Warehouse()
{
Name = "Foo Warehousing Ltd.",
Address = "No. 10, Some Ave., Some City, Some Country",
Area = 120000.50 // square meters
};
// Now serialize the instance
YAXSerializer serializer = new YAXSerializer(typeof(Warehouse));
string someString = serializer.Serialize(w);
</pre>
<p>After executing the above code, the variable <code>someS<code></code>tring</code> will contain:</p>
<pre lang="xml"><Warehouse>
<Name>Foo Warehousing Ltd.</Name>
<Address>No. 10, Some Ave., Some City, Some Country</Address>
<Area>120000.5</Area>
</Warehouse></pre>
<p>
Note that there are other overloads of the <code>Serialize</code> method that accept
instances of <code>XmlWriter</code> and <code>TextWriter</code>. So far the result is much alike <code>
XmlSerializer</code> class of the .NET Framework. Let's make some
modifications. We want the <code>Name</code> property to be serialized as an attribute for
the <code>Warehouse</code> base element, also we want that <code>Address</code> property be the <code>value</code> attribute for
an element named <code>Location</code>, and <code>Area</code> be the <code>value</code> attribute for an element named
<code>Area</code>. We modify the <code>Warehouse</code> class by adding proper attributes to its
properties:</p>
<pre lang="cs">
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("value")]
[YAXAttributeFor("Location")]
public string Address { get; set; }
[YAXSerializeAs("value")]
[YAXAttributeFor("Area")]
public double Area { get; set; }
}
</pre>
<p>By the above modifications our object will be serialized as:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<Location value="No. 10, Some Ave., Some City, Some Country" />
<Area value="120000.5" />
</Warehouse></pre>
<p>Make special attention to the combination of the <code>YAXSerializeAs</code> and
<code>YAXAttributeFor</code> attributes for the last two properties. The <code>YAXSerializeAs</code>
attribute specifies a new name (alias) for the property to be shown by in the
XML file, but the <code>YAXAttributeFor</code>
attribute states that the property should be serialized as an attribute for the
element whose name is provided in the parameter. The corresponding attribute for creating elements is <code>YAXElementFor</code>. Note the usage of
<code>YAXElementFor</code> attribute for the <code>Area</code> property in the following example:</p>
<pre lang="cs">
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
}
</pre>
<p>And the form of its XML serialization output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
</Warehouse>
</pre>
<p>Now let's define an enumeration of all possible items that can be stored in a
warehouse in general, and then specify the subset of items that is stored in our
hypothetical warehouse. The <code>PossibleItems</code> enum is the mentioned enumeration and
the <code>Items</code> array is the mentioned subset as defined in the following code
snippet:</p>
<pre lang="cs">
public enum PossibleItems
{
Item1, Item2, Item3, Item4, Item5, Item6,
Item7, Item8, Item9, Item10, Item11, Item12
}
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
public PossibleItems[] Items { get; set; }
}
</pre>
<p>We modify the instance creation code as following, while the serialization code
remains intact as before:</p>
<pre lang="cs">
Warehouse w = new Warehouse()
{
Name = "Foo Warehousing Ltd.",
Address = "No. 10, Some Ave., Some City, Some Country",
Area = 120000.50, // square meters
Items = new PossibleItems[] { PossibleItems.Item3, PossibleItems.Item6, PossibleItems.Item9, PossibleItems.Item12 }
};
</pre>
<p>
The XML generated by serialization is:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<Items>
<PossibleItems>Item3</PossibleItems>
<PossibleItems>Item6</PossibleItems>
<PossibleItems>Item9</PossibleItems>
<PossibleItems>Item12</PossibleItems>
</Items>
</Warehouse>
</pre>
<p>
But we are not satisfied with the result, so let's change the <code>Items</code> element name
to <code>StoreableItems</code> and change each child element name from <code>PossibleItems</code> simply
to <code>Item</code>. Here's the modifications made to the <code>Warehouse</code> class:</p>
<pre lang="cs">
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Recursive, EachElementName="Item")]
[YAXSerializeAs("StoreableItems")]
public PossibleItems[] Items { get; set; }
}
</pre>
<p>
And here's the XML output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>
<Item>Item3</Item>
<Item>Item6</Item>
<Item>Item9</Item>
<Item>Item12</Item>
</StoreableItems>
</Warehouse>
</pre>
<p>
How about serializing the array as a comma seperated list of items? Modify the
<code>Warehouse</code> class as:</p>
<pre lang="cs">
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Serially, SeparateBy=", ")]
[YAXSerializeAs("StoreableItems")]
public PossibleItems[] Items { get; set; }
}
</pre>
<p>
and view the corresponding XML output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>Item3, Item6, Item9, Item12</StoreableItems>
</Warehouse>
</pre>
<p>
The latter two examples need more explanation of the <code>YAXCollection</code> attribute.
The first parameter passed to this attribute is the type of the serialization of
the collection classes, which there are two of them defined in the library. One is called <code>Recursive</code> and the other is called
<code>Serially</code>. By <code>Recursive</code> we mean that every member of the collection object is serialized as a child element
of the collection itself. If <code>Recursive</code> is selected, then the developer may choose to rename each child element name
by providing the <code>EachElementName</code> named parameter of the attribute. The second type of serialization of
the collection classes is called <code>Serially</code>, and by which we mean that child elements of the collection object
are serialized in only one element all seperated by some delimiter specified by the <code>SeparateBy</code> named parameter.</p>
<p>
Now let's improve our example a little more by adding a dictionary of, say,
<code>PossibleItems</code>, and their quantity, as stored in our hypothetical warehouse.</p>
<pre lang="cs">
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Serially, SeparateBy=", ")]
[YAXSerializeAs("StoreableItems")]
public PossibleItems[] Items { get; set; }
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
}
</pre>
<p>
We instantiate an object as follows:</p>
<pre lang="cs">
Dictionary<PossibleItems,int> dicItems = new Dictionary<PossibleItems,int>();
dicItems.Add(PossibleItems.Item3, 10);
dicItems.Add(PossibleItems.Item6, 120);
dicItems.Add(PossibleItems.Item9, 600);
dicItems.Add(PossibleItems.Item12, 25);
Warehouse w = new Warehouse()
{
Name = "Foo Warehousing Ltd.",
Address = "No. 10, Some Ave., Some City, Some Country",
Area = 120000.50, // square meters
Items = new PossibleItems[] { PossibleItems.Item3, PossibleItems.Item6, PossibleItems.Item9, PossibleItems.Item12 },
ItemQuantitiesDic = dicItems
};
</pre>
<p>
And here's the XML output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>Item3, Item6, Item9, Item12</StoreableItems>
<ItemQuantitiesDic>
<KeyValuePairOfPossibleItemsInt32>
<Key>Item3</Key>
<Value>10</Value>
</KeyValuePairOfPossibleItemsInt32>
<KeyValuePairOfPossibleItemsInt32>
<Key>Item6</Key>
<Value>120</Value>
</KeyValuePairOfPossibleItemsInt32>
<KeyValuePairOfPossibleItemsInt32>
<Key>Item9</Key>
<Value>600</Value>
</KeyValuePairOfPossibleItemsInt32>
<KeyValuePairOfPossibleItemsInt32>
<Key>Item12</Key>
<Value>25</Value>
</KeyValuePairOfPossibleItemsInt32>
</ItemQuantitiesDic>
</Warehouse>
</pre>
<p>
Yet we can improve the output by renaming the incomprehensible element names,
e.g. renaming <code>ItemQuantitiesDic</code> to simply <code>ItemQuantities</code> and <code>KeyValuePairOfPossibleItemsInt32</code>
(which is the friendly name of <code>KeyValuePair<PossibleItems, Int32></code>) to, say,
<code>ItemInfo</code>. So we add proper attributes to the definition of <code>ItemQuantitiesDic</code> as
follows:</p>
<pre lang="cs">
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Serially, SeparateBy=", ")]
[YAXSerializeAs("StoreableItems")]
public PossibleItems[] Items { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Recursive, EachElementName="ItemInfo")]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
}
</pre>
<p>
And here's the XML output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>Item3, Item6, Item9, Item12</StoreableItems>
<ItemQuantities>
<ItemInfo>
<Key>Item3</Key>
<Value>10</Value>
</ItemInfo>
<ItemInfo>
<Key>Item6</Key>
<Value>120</Value>
</ItemInfo>
<ItemInfo>
<Key>Item9</Key>
<Value>600</Value>
</ItemInfo>
<ItemInfo>
<Key>Item12</Key>
<Value>25</Value>
</ItemInfo>
</ItemQuantities>
</Warehouse>
</pre>
<p>
One question that arises at the time is that, is it possible to rename <code>Key</code> or <code>Value</code>
elements or reformat the parent <code>ItemInfo</code> element? The answer currently is no, because,
these are instances of the <code>KeyValuePair<,></code> class that are serialized, and we do
not have access to the definition of this class to add formatting attributes to its
properties. If one needs special formatting of the output, he/she has to define
his/her custom classes (with proper formatting attributes) and use them instead of
<code>KeyValuePair<,></code> in some collection class (e.g. <code>List<MyCustomItemHolder></code>).</p>
<p>
Another question is that, is it possible to serialize members of our dictionary
serially? Again the answer is no. Only collections of basic data types (which
are
primitive data types, enums, string, and the <code>DateTime</code> structure)
can be serialized serially with no overhead for the developer (More precisely speaking, everything can be serialized serially,
but they cannot be <i>deserialized</i> if they are <i>not</i> among basic data
types).</p>
<p>
As the last improvement let's see how nested objects are serialized. We define
a <code>Person</code> class and add an <code>Owner</code> property of type <code>Person</code> to our hypothetical
warehouse.</p>
<pre lang="cs">
public class Person
{
public string SSN { get; set; }
public string Name { get; set; }
public string Family { get; set; }
public int Age { get; set; }
}
public class Warehouse
{
[YAXAttributeForClass()]
public string Name { get; set; }
[YAXSerializeAs("address")]
[YAXAttributeFor("SiteInfo")]
public string Address { get; set; }
[YAXSerializeAs("SurfaceArea")]
[YAXElementFor("SiteInfo")]
public double Area { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Serially, SeparateBy=", ")]
[YAXSerializeAs("StoreableItems")]
public PossibleItems[] Items { get; set; }
[YAXCollection(YAXCollectionSerializationTypes.Recursive, EachElementName="ItemInfo")]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
public Person Owner { get; set; }
}
</pre>
<p>And create an instance as follows:</p>
<pre lang="cs">
Dictionary<PossibleItems,int> dicItems = new Dictionary<PossibleItems,int>();
dicItems.Add(PossibleItems.Item3, 10);
dicItems.Add(PossibleItems.Item6, 120);
dicItems.Add(PossibleItems.Item9, 600);
dicItems.Add(PossibleItems.Item12, 25);
Warehouse w = new Warehouse()
{
Name = "Foo Warehousing Ltd.",
Address = "No. 10, Some Ave., Some City, Some Country",
Area = 120000.50, // square meters
Items = new PossibleItems[] { PossibleItems.Item3, PossibleItems.Item6, PossibleItems.Item9, PossibleItems.Item12 },
ItemQuantitiesDic = dicItems,
Owner = new Person() { SSN = "123456789", Name = "John", Family = "Doe", Age = 50 }
};
</pre>
<p>
Here is the XML output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>Item3, Item6, Item9, Item12</StoreableItems>
<ItemQuantities>
<ItemInfo>
<Key>Item3</Key>
<Value>10</Value>
</ItemInfo>
<ItemInfo>
<Key>Item6</Key>
<Value>120</Value>
</ItemInfo>
<ItemInfo>
<Key>Item9</Key>
<Value>600</Value>
</ItemInfo>
<ItemInfo>
<Key>Item12</Key>
<Value>25</Value>
</ItemInfo>
</ItemQuantities>
<Owner>
<SSN>123456789</SSN>
<Name>John</Name>
<Family>Doe</Family>
<Age>50</Age>
</Owner>
</Warehouse></pre>
<p>
Again we add more formatting attributes to both of the classes. For example we
change the <code>Person</code> class as follows: </p>
<pre lang="cs">
public class Person
{
[YAXAttributeForClass()]
public string SSN { get; set; }
[YAXAttributeFor("Identification")]
public string Name { get; set; }
[YAXAttributeFor("Identification")]
public string Family { get; set; }
public int Age { get; set; }
}
</pre>
<p>
The above modification produces the following output:</p>
<pre lang="xml">
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>Item3, Item6, Item9, Item12</StoreableItems>
<ItemQuantities>
<ItemInfo>
<Key>Item3</Key>
<Value>10</Value>
</ItemInfo>
<ItemInfo>
<Key>Item6</Key>
<Value>120</Value>
</ItemInfo>
<ItemInfo>
<Key>Item9</Key>
<Value>600</Value>
</ItemInfo>
<ItemInfo>
<Key>Item12</Key>
<Value>25</Value>
</ItemInfo>
</ItemQuantities>
<Owner SSN="123456789">
<Identification Name="John" Family="Doe" />
<Age>50</Age>
</Owner>
</Warehouse>
</pre>
<h2>
Deserialization and Error Handling Schemes
</h2>
<p>
Assume that we successfully serialized an object of our hypothetical warehouse
into XML, and stored it into some file. In order to deserialize the XML back into
an object, use the <code>Deserialize</code> method in the following style:</p>
<pre lang="cs">
YAXSerializer ser = new YAXSerializer(typeof(Warehouse));
try
{
object o = ser.Deserialize(someStringContainingXML);
if (o != null)
// means that the XML input has been deserialized successfully
else
// the XML input cannot be deserialized successfully
}
catch
{
}
</pre>
<p>
It is generally inconvenient to have the content of the XML stored in some
string variable as in the example above. For this reason there are overloads of the
<code>Deserialize</code> method that accept as parameter instances of <code>XmlReader</code> or <code>TextReader</code>.
Also there is another method called <code>DeserializeFromFile</code> which accepts as parameter
the path to the XML input file.</p>
<p>
No matter which method is used, always use the above style
for deserialization, i.e. put the deserialization method within a <code>try</code> block, and
always compare the result with <code>null</code>.</p>
<p>
The developer cannot assume that the deserialization process always succeeds
especially if the XML file
has been edited by a human user. The problems that may occur are mal-formed XML
file, missing elements or attributes for which a property exists, and several others. To
get a better view of different problems which might happen in YAXLib see the
section: <a href="#exceptions">YAXLib Exception Classes</a>. To give the
developers more control on handling of the errors there are some extra features
added to YAXLib.</p>
<p>
One feature is the <code>ParsingErrors</code> property of the <code>YAXSerializer</code>
class. This property is a reference to a collection of <code>YAXException</code>s that
has happened
during deserialization or serialization but has not been thrown. Actually this
collection serves as the logger of the library (we later discuss what is meant
by not thrown exceptions). The developer may choose later to loop
through the exceptions, or simply call the overloaded <code>ToString()</code> method to print
the list of exceptions in a neat format (this is what is done in the demo
application).</p>
<p>
Another feature is enabling the developers to choose the exception handling policy of
the library. This is done by providing the <code>YAXExceptionHandlingPolicies</code> parameter of the
constructor of the <code>YAXSerializer</code>. This is an <code>enum</code> with three possible values:</p>
<ul>
<li><code>ThrowWarningsAndErrors</code>: throws an exception at the time that any
problem
(labeled as warning or error) occurs.
In this case the deserialization process cannot continue and the operation
fails. This is the default policy if not specified otherwise by the developer.</li>
<li><code>ThrowErrorsOnly</code>: only throws exceptions corresponding to those
labeled as error, and only logs those that are labeled as warning which might be
accessed later by the <code>ParsingErrors</code> property.</li>
<li><code>DoNotThrow</code>: logs all kinds of exceptions (either labeled as warning
or error) and do not throw any of them. The <code>YAXCannotSerializeSelfReferentialTypes</code>
and <code>YAXObjectTypeMismatch</code> are two exceptions of this rule, but note
that both of these two exception classes happen only at serialization time, not
during deserialization.</li>
</ul>
<p>
The 3rd parameter of the constructor of the <code>YAXSerializer</code> class
specifies the default exception type (warning or error) for all exceptions that may happen
(the default is <code>Error</code>). One can alter this behavior for a special
property of the class he/she wants to serialize by using the <code>YAXErrorIfMissed</code>
attribute. This attribute, also lets the developer to pick a default value for the
property, which is used if the data corresponding to that property is missed
in the input XML. As an example consider the <code>ProgrammingLanguage</code> class below.</p>
<pre lang="cs">
public class ProgrammingLanguage
{
public string LanguageName { get; set; }
public bool IsCaseSensitive { get; set; }
}
</pre>
<p>
We require that <code>LanguageName</code> property be mandatory, but <code>IsCaseSensitive</code> property
be optional. If the value corresponding to <code>IsCaseSensitive</code> property is missed in
the XML, then we consider it as <code>true</code>. For this purpose we add
attributes to the class definition as shown below:</p>
<pre lang="cs">
public class ProgrammingLanguage
{
[YAXErrorIfMissed(YAXExceptionTypes.Error)]
public string LanguageName { get; set; }
[YAXErrorIfMissed(YAXExceptionTypes.Warning, DefaultValue=true)]
public bool IsCaseSensitive { get; set; }
}
</pre>
<p>
As seen in the above code snippet, we treat absence of <code>LanguageName</code> as an error,
but we treat absence of <code>IsCaseSensitive</code> as a warning, and if this happens we
assign the default value of <code>true</code> to this property. Consequently we use the
<code>ThrowErrorsOnly</code> exception handling policy and treat all other exceptions as
warnings. The code below illustrates this while assumes that the input XML is
stored in the string variable <code>inputXML</code>.</p>
<pre lang="cs">
YAXSerializer serializer = new YAXSerializer(typeof(ProgrammingLanguage),
YAXExceptionHandlingPolicies.ThrowErrorsOnly,
YAXExceptionTypes.Warning);
object deserializedObject = null;
try
{
deserializedObject = serializer.Deserialize(inputXML);
if (serializer.ParsingErrors.ContainsAnyError)
{
Console.WriteLine("Succeeded to deserialize, but these problems also happened:");
Console.WriteLine(serializer.ParsingErrors.ToString());
}
}
catch (YAXException ex)
{
Console.WriteLine("Failed to deserialize input, this is the error occurred:");
Console.WriteLine(ex.ToString());
}
</pre>
<p>
If the input XML is in the following format, then the deserialization finishes
successfully:</p>
<pre lang="xml">
<ProgrammingLanguage>
<LanguageName>C#</LanguageName>
<IsCaseSensitive>true</IsCaseSensitive>
</ProgrammingLanguage>
</pre>
<p>
The following XML input, leads to a warning which will be stored in the
<code>ParsingErrors</code> collection, but the value of <code>IsCaseSensitive</code> will be set to <code>true</code>.</p>
<pre lang="xml">
<ProgrammingLanguage>
<LanguageName>C#</LanguageName>
<!-- <IsCaseSensitive>true</IsCaseSensitive> -->
</ProgrammingLanguage>
</pre>
<p>
But the following XML input, leads to an exception being thrown, and therefore
the deserialization process fails.</p>
<pre lang="xml">
<ProgrammingLanguage>
<!-- <LanguageName>C#</LanguageName> -->
<IsCaseSensitive>true</IsCaseSensitive>
</ProgrammingLanguage>
</pre>
<h2>
The Problem with Self-Referential Classes</h2>
<p>
Self-referential classes are those that have a property of the same type as the
class itself. Self-referential classes cannot be serialized, because they lead
to infinite recursion, and hence stack-overflow. The
<code>YAXCannotSerializeSelfReferentialTypes</code> exception is thrown if such classes are
encountered during the serialization process. This is the only YAXLib exception
that cannot be turned off even if the exception-handling policy is set to
<code>DoNotThrow</code>. So it is recommended that you always put your serialization code
within <code>try</code> blocks.</p>
<p>
As an example the <code>System.Drawing.Color</code> struct of the .NET Framework is
self-referential (because it has a public property of type <code>Color</code>, and YAXLib is
only interested in properties). To overcome this problem one can create
properties of some other types (e.g. <code>string</code>) in which the <code>get</code> and <code>set</code> methods
convert between the two types (e.g. convert <code>string</code> to <code>Color</code> and vice versa).
Here's an example that uses a property of type <code>string</code> instead of a property of
type <code>Color</code>:</p>
<pre lang="cs">
public class SelfReferentialExample
{
private Color m_color = Color.Blue;
public string TheColor
{
get
{
return String.Format("#{0:X}", m_color.ToArgb());
}
set
{
m_color = Color.White;
value = value.Trim();
if (value.StartsWith("#")) // remove leading # if any
value = value.Substring(1);
int n;
if (Int32.TryParse(value, System.Globalization.NumberStyles.HexNumber, null, out n))
{
m_color = Color.FromArgb(n);
}
}
}
}
</pre>
<p>Here is the XML generated by serializing an object of the above class:</p>
<pre lang="xml">
<SelfReferentialExample>
<TheColor>#FF0000FF</TheColor>
</SelfReferentialExample>
</pre>
<h2>YAXLib Attributes</h2>
<p>
In this section we list and describe all attribute classes provided in the
YAXLib library.
</p>
<ol>
<li><b><code>YAXSerializeAs</code></b>: Defines an alias for the property
or class under which it will be serialized. This attribute is applicable to
properties and classes only.</li>
<li><b><code>YAXDontSerialize</code></b>: Prevents serialization of some property.
This attribute is applicable to properties only.</li>
<li><b><code>YAXAttributeForClass</code></b>: Makes a property to appear as an
attribute for the class (i.e. the parent element) if possible. This attribute is
applicable to properties only.</li>
<li><b><code>YAXAttributeFor</code></b>: Makes a property to appear
as an attribute for another element, if possible. This attribute is applicable
to properties only.</li>
<li><b><code>YAXElementFor</code></b>: Makes a property to appear as
a child element for another element. This attribute is applicable to properties
only.</li>
<li><b><code>YAXCollection</code></b>: Controls the serialization of
collection instances. This attribute is applicable to properties only. For more
details and examples see section: A Tutorial of YAXLib Usage</li>
<li><b><code>YAXErrorIfMissed</code></b>: Specifies the behavior of
the deserialization method, if the element/attribute corresponding to this
property is missed in the XML input. This attribute is applicable to properties
only.</li>
</ol>
<h2><a name="exceptions"></a>YAXLib Exception Classes</h2>
<p>
In this section we list and describe all exception classes which might be thrown
or logged as error, by YAXLib. Note that all the exception classes in YAXLib are
derived from <code>YAXException</code> class.</p>
<ul>
<li>Exceptions raised during serialization<ol>
<li><b><code>YAXAttributeAlreadyExistsException</code></b>: Raised when trying to serialize an attribute
where another attribute with the same name already exists.</li>
<li><b><code>YAXCannotSerializeSelfReferentialTypes</code></b>: Raised when trying to serialize
self-referential types. This exception cannot be turned off.</li>
<li><b><code>YAXObjectTypeMismatch</code></b>: Raised when the object provided for serialization is not
of the type provided for the serializer. This exception cannot be turned off.</li>
</ol>
</li>
<li>Exceptions raised during deserialization<ol>
<li><b><code>YAXAttributeMissingException</code></b>: Raised when the attribute corresponding to some
property is not present in the given XML file.</li>
<li><b><code>YAXElementMissingException</code></b>: Raised when the element corresponding to some
property is not present in the given XML file.</li>
<li><b><code>YAXBadlyFormedInput</code></b>: Raised when the value provided for some property in the XML
input, cannot be converted to the type of the property.</li>
<li><b><code>YAXPropertyCannotBeAssignedTo</code></b>: Raised when the value provided for some property
in the XML input, cannot be assigned to the property.</li>
<li><b><code>YAXCannotAddObjectToCollection</code></b>: Raised when some member of the collection in the
input XML, cannot be added to the collection object.</li>
<li><b><code>YAXDefaultValueCannotBeAssigned</code></b>: Raised when the default value specified by the <code>
YAXErrorIfMissedAttribute</code> could not be assigned to the property.</li>
<li><b><code>YAXBadlyFormedXML</code></b>: Raised when the XML input does not follow standard XML
formatting rules.</li>
</ol>
</li>
</ul>
<h2>When Not to Use YAXLib</h2>
<p>
The emphasis of YAXLib on the formatting of the generated XML output and ignoring
the deserialization errors as far as possible (of course as specified by the
developer), shows that it is more suitable for communication between program and
human users. If your sole purpose is just to store the internal status of some
object to be loaded later, and it is <i>not</i> going to be edited by a human user
later, and your objects do not use collection classes such as <code>Dictionary<,></code>;
then it might
be better to use the <code>XmlSerializer</code> class of the .NET Framework.</p>
<p>
YAXLib assumes that the public properties of the objects, are all that is needed
to be serialized. <a target="_blank" href="http://www.codeproject.com/KB/XML/XmlSerializer.aspx">
Here</a> you can find another work that takes into account private fields also.</p>
<h2><a name="issues"></a>Known Issues / Shortcomings</h2>
<p>
Try to use YAXLib with generic collection types only (e.g. use <code>Dictionary<,></code>
instead of <code>Hashtable</code>). </p>
<p>
The behavior of the library is undefined when it is
working on non-generic collections of heterogeneous members (e.g. a non-generic
<code>ArrayList</code> that contains integers and strings and other type of
objects at the same
time).</p>
<p>
YAXLib is not able to define alias for <code>enum</code> items (maybe a future work).</p>
<h2>History</h2>
<ul>
<li> <b>March 13, 2009</b>: 1st version.</li>
</ul>
</body>
</html>