Contents
Introduction
In this article, an XML serialization library is presented which is referred to hereafter as YAXLib. The .NET Framework has several serialization capabilities, specially XmlSerializer
, which is widely used by developers to serialize objects to XML and deserialize later. My problems with the XmlSerializer
class of the .NET framework were:
- The developer is not free to choose the structure of the generated XML.
- It does not support serializing some collection classes (e.g.,
Dictionary<,>
) or properties of type IEnumerable<>
. - When deserializing, it fails when some fields are not present, making it unsuitable for storing configuration files, which might be edited later by human users.
Why Use YAXLib
The features of YAXLib that solve the above problems are:
- The user can decide on the structure of the XML file. A property can be a child element, or attribute for another property, or an element that has no corresponding property in the class.
- The collection classes can be serialized as a comma-separated (or any other separator) list of data items. Also, there are special formatting capabilities designed for objects of type
Dictionary<,>
, so that the developer has full control on the appearance of the generated XML. - It supports serialization and deserialization of all the generic collection classes within the
System.Collections.Generic
namespace (namely Dictionary
, HashSet
, LinkedList
, List
, Queue
, SortedDictionary
, SortedList
, and Stack
) and all the non-generic collection classes within the System.Collections
namespace (namely ArrayList
, BitArray
, Hashtable
, Queue
, SortedList
, and Stack
). The non-generic collection classes could contain objects of heterogeneous types. Also, it supports serialization and deserialization of single-dimensional, multi-dimensional, and jagged arrays. - It supports serializing and deserializing objects and collections of items, through a reference to their base class, or interface.
- It supports multi-stage deserialization.
- The developer can provide XML comments within the XML output.
- When it comes to deserialization, the developer can choose what happens when data related to some property is not present in the XML file. This situation can be regarded as an error so the library throws some exception or logs it as an error, or it can be regarded as a warning and some predefined default value specified by the developer is assigned to the absent property. Also, the developer may choose to ignore this problem, so that the related exception is neither thrown nor logged. See the section Preserving Null-References Identity to see when ignoring absent data might be useful.
- 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.
A Tutorial of YAXLib Usage
Basic Usage and Formatting
Let's see a very simple example, and then incrementally improve it to reflect different aspects of YAXLib. To start, we define a simple Warehouse
class:
public class Warehouse
{
public string Name { get; set; }
public string Address { get; set; }
public double Area { get; set; }
}
The following code snippet illustrates what is needed to serialize an object of the Warehouse
class:
Warehouse w = new Warehouse()
{
Name = "Foo Warehousing Ltd.",
Address = "No. 10, Some Ave., Some City, Some Country",
Area = 120000.50
};
YAXSerializer serializer = new YAXSerializer(typeof(Warehouse));
string someString = serializer.Serialize(w);
After executing the above code, the variable someS<code>
tring will contain:
<Warehouse>
<Name>Foo Warehousing Ltd.</Name>
<Address>No. 10, Some Ave., Some City, Some Country</Address>
<Area>120000.5</Area>
</Warehouse>
Note that there are other overloads of the Serialize
method that accept instances of XmlWriter
and TextWriter
. So far, the result is much alike that of the XmlSerializer
class of the .NET Framework. Let's make some modifications. We want the Name
property to be serialized as an attribute for the Warehouse
base element; also, we want the Address
property to be the value
attribute for an element named Location
, and Area
be the value
attribute for an element named Area
. We modify the Warehouse
class by adding proper attributes to its properties:
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; }
}
With the above modifications, our object will be serialized as:
<Warehouse Name="Foo Warehousing Ltd.">
<Location value="No. 10, Some Ave., Some City, Some Country" />
<Area value="120000.5" />
</Warehouse>
Pay special attention to the combination of the YAXSerializeAs
and YAXAttributeFor
attributes for the last two properties. The YAXSerializeAs
attribute specifies a new name (alias) for the property to be shown in the XML file, but the YAXAttributeFor
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 YAXElementFor
. Note the usage of the YAXElementFor
attribute for the Area
property in the following example:
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; }
}
And, the form of its XML serialization output:
<Warehouse Name="Foo Warehousing Ltd.">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
</Warehouse>
Serializing Objects of Collection Classes
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 PossibleItems
enum is the mentioned enumeration, and the Items
array is the mentioned subset, as defined in the following code snippet:
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; }
}
We modify the instance creation code as follows, while the serialization code remains intact as before:
Warehouse w = new Warehouse()
{
Name = "Foo Warehousing Ltd.",
Address = "No. 10, Some Ave., Some City, Some Country",
Area = 120000.50,
Items = new PossibleItems[] { PossibleItems.Item3, PossibleItems.Item6,
PossibleItems.Item9, PossibleItems.Item12 }
};
The XML generated by the serialization is:
<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>
But, we are not satisfied with the result, so let's change the Items
element name to StoreableItems
, and change each child element name from PossibleItems
to Item
. Here are the modifications made to the Warehouse
class:
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; }
}
And, here's the XML output:
<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>
How about serializing the array as a comma separated list of items? Modify the Warehouse
class as:
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; }
}
and view the corresponding XML output:
<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>
The latter two examples need more explanation of the YAXCollection
attribute. The first parameter passed to this attribute is the type of serialization of the collection classes, and there are three of them defined in the library: one is called Recursive
, another is RecursiveWithNoContainingElement
, and the third one is Serially
. By Recursive
, we mean that every member of the collection object is serialized as a child element of the collection itself. If Recursive
is selected, then the developer may choose to rename each child element name by providing the EachElementName
named parameter of the attribute. The second type of serialization of the collection classes is RecursiveWithNoContainingElement
, which operates just like Recursive
except that the collection itself does not have a separate element for itself. All the collection items will be serialized as if they are members of the containing class. Finally, the third type of serialization of the collection classes is called Serially
, and by which we mean that child elements of the collection object are serialized in only one element, all separated by some delimiter specified by the SeparateBy
named parameter.
Also, you can set the IsWhiteSpaceSeparator
named parameter to false
so that white-space characters within the members themselves are not considered as separators. As an example, see the PathsExample
class in the demo application.
Serializing Objects of Type Dictionary
Let's improve our example a little more by adding a dictionary of, say, PossibleItem
s, and their quantity, as stored in our hypothetical warehouse.
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; }
}
We instantiate an object as follows:
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,
Items = new PossibleItems[] { PossibleItems.Item3,
PossibleItems.Item6,
PossibleItems.Item9,
PossibleItems.Item12 },
ItemQuantitiesDic = dicItems
};
Here's the XML output:
<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>
We can still improve the output by renaming the incomprehensible element names, e.g., renaming ItemQuantitiesDic
to simply ItemQuantities
, and KeyValuePairOfPossibleItemsInt32
(which is the friendly name of KeyValuePair<PossibleItems, Int32>
) to, say, ItemInfo
. These (simple) changes are possible with the YAXCollection
attribute, but we want to introduce the YAXDictionary
attribute, which provides more formatting options. So, we add proper attributes to the definition of ItemQuantitiesDic
, as follows:
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; }
[YAXDictionary(EachPairName="ItemInfo")]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
}
And, here's the XML output:
<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>
How about making it more human-readable? For example, let's rename Key
to Item
and Value
to, say, Count
. Also, the result seems less complex if we make both the Key
and Value
parts attributes of the parent ItemInfo
element. To accomplish this, make the following changes to the class definition:
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; }
[YAXDictionary(EachPairName="ItemInfo", KeyName="Item", ValueName="Count",
SerializeKeyAs=YAXNodeTypes.Attribute,
SerializeValueAs=YAXNodeTypes.Attribute)]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
}
Here's the XML output:
<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 Item="Item3" Count="10" />
<ItemInfo Item="Item6" Count="120" />
<ItemInfo Item="Item9" Count="600" />
<ItemInfo Item="Item12" Count="25" />
</ItemQuantities>
</Warehouse>
It now seems more readable and less complicated. The YAXDictionary
attribute needs more explanation. This attribute can receive seven parameters:
EachPairName
: defines an alias for the element containing the key-value pair.KeyName
: defines an alias for the Key
element/attribute.ValueName
: defines an alias for the Value
element/attribute.SerializeKeyAs
: specifies whether the key-part should be serialized as an attribute of its containing element, or as its child element.SerializeValueAs
: specifies whether the value-part should be serialized as an attribute of its containing element, or as its child element.KeyFormatString
: defines a format string, by which the key data will be serialized.ValueFormatString
: defines a format string, by which the value data will be serialized.
It is also possible to use both the YAXDictionary
and YAXCollection
attributes together to produce better results. For example, consider that you want the ItemInfo
elements of the previous example be serialized without the containing element ItemQuantities
. For this purpose, you can add a YAXCollection
attribute, with its serialization type set to RecursiveWithNoContainingElement
, as the following code snippet depicts:
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.RecursiveWithNoContainingElement)]
[YAXDictionary(EachPairName="ItemInfo", KeyName="Item", ValueName="Count",
SerializeKeyAs=YAXNodeTypes.Attribute,
SerializeValueAs=YAXNodeTypes.Attribute)]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
}
Here's the XML output:
<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>
<ItemInfo Item="Item3" Count="10" />
<ItemInfo Item="Item6" Count="120" />
<ItemInfo Item="Item9" Count="600" />
<ItemInfo Item="Item12" Count="25" />
</Warehouse>
Serializing Nested Objects
As another improvement, let's see how nested objects are serialized. We define a Person
class and add an Owner
property of type Person
to our hypothetical warehouse.
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; }
[YAXDictionary(EachPairName = "ItemInfo", KeyName = "Item", ValueName = "Count",
SerializeKeyAs = YAXNodeTypes.Attribute,
SerializeValueAs = YAXNodeTypes.Attribute)]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
public Person Owner { get; set; }
}
And, create an instance as follows:
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,
Items = new PossibleItems[] { PossibleItems.Item3, PossibleItems.Item6,
PossibleItems.Item9, PossibleItems.Item12 },
ItemQuantitiesDic = dicItems,
Owner = new Person() { SSN = "123456789", Name = "John",
Family = "Doe", Age = 50 }
};
Here is the XML output:
<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 Item="Item3" Count="10" />
<ItemInfo Item="Item6" Count="120" />
<ItemInfo Item="Item9" Count="600" />
<ItemInfo Item="Item12" Count="25" />
</ItemQuantities>
<Owner>
<SSN>123456789</SSN>
<Name>John</Name>
<Family>Doe</Family>
<Age>50</Age>
</Owner>
</Warehouse>
Again, we add more formatting attributes to both of the classes. For example, we change the Person
class as follows:
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; }
}
The above modification produces the following output:
<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 Item="Item3" Count="10" />
<ItemInfo Item="Item6" Count="120" />
<ItemInfo Item="Item9" Count="600" />
<ItemInfo Item="Item12" Count="25" />
</ItemQuantities>
<Owner SSN="123456789">
<Identification Name="John" Family="Doe" />
<Age>50</Age>
</Owner>
</Warehouse>
How about making the owner's SSN appear as an attribute of the Warehouse
itself? It is possible in YAXLib to specify serialization addresses, as is popular in file systems. For example, you can use YAXAttributeFor
in the following styles:
[YAXAttributeFor("Identification")]
[YAXAttributeFor("./Identification")]
[YAXAttributeFor("../Identification")]
[YAXAttributeFor("../../Identification")]
[YAXAttributeFor("SomeTag/SomeOtherTag/AndSo")]
We want SSN to appear as an attribute for its parent, so we set its serialization location as follows:
public class Person
{
[YAXAttributeFor("..")]
[YAXSerializeAs("OwnerSSN")]
public string SSN { get; set; }
[YAXAttributeFor("Identification")]
public string Name { get; set; }
[YAXAttributeFor("Identification")]
public string Family { get; set; }
public int Age { get; set; }
}
The XML result is:
<Warehouse Name="Foo Warehousing Ltd." OwnerSSN="123456789">
<SiteInfo address="No. 10, Some Ave., Some City, Some Country">
<SurfaceArea>120000.5</SurfaceArea>
</SiteInfo>
<StoreableItems>Item3, Item6, Item9, Item12</StoreableItems>
<ItemQuantities>
<ItemInfo Item="Item3" Count="10" />
<ItemInfo Item="Item6" Count="120" />
<ItemInfo Item="Item9" Count="600" />
<ItemInfo Item="Item12" Count="25" />
</ItemQuantities>
<Owner>
<Identification Name="John" Family="Doe" />
<Age>50</Age>
</Owner>
</Warehouse>
Adding XML Comments
Since one of the main goals of YAXLib is enhancing human readability, it seems useful to have some means of inserting XML comments in the generated XML. You can use the YAXComment
attribute to add comments to serialized objects. For example, see the modified version of our Warehouse
below:
[YAXComment("General information about a warehouse")]
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; }
[YAXDictionary(EachPairName = "ItemInfo", KeyName = "Item", ValueName = "Count",
SerializeKeyAs = YAXNodeTypes.Attribute,
SerializeValueAs = YAXNodeTypes.Attribute)]
[YAXSerializeAs("ItemQuantities")]
public Dictionary<PossibleItems, int> ItemQuantitiesDic { get; set; }
[YAXComment("All the fields are mandatory, please fill all of them")]
public Person Owner { get; set; }
}
and the generated 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 Item="Item3" Count="10" />
<ItemInfo Item="Item6" Count="120" />
<ItemInfo Item="Item9" Count="600" />
<ItemInfo Item="Item12" Count="25" />
</ItemQuantities>
<Owner SSN="123456789">
<Identification Name="John" Family="Doe" />
<Age>50</Age>
</Owner>
</Warehouse>
Deserialization and Error Handling Schemes
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 Deserialize
method in the following style:
YAXSerializer ser = new YAXSerializer(typeof(Warehouse));
try
{
object o = ser.Deserialize(someStringContainingXML);
if (o != null)
else
}
catch
{
}
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 Deserialize
method that accept instances of XmlReader
or TextReader
as a parameter. Also, there is another method called DeserializeFromFile
which accepts the path to the XML input file as a parameter.
No matter which method is used, always use the above style for deserialization; i.e., put the deserialization method within a try
block, and always compare the result with null
.
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 a 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: YAXLib Exception Classes. To give the developer more control on handling errors, there are some extra features added to YAXLib.
One feature is the ParsingErrors
property of the YAXSerializer
class. This property is a reference to a collection of YAXException
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 ToString()
method to print the list of exceptions in a neat format (this is what is done in the demo application).
Another feature is enabling developers to choose the exception handling policy of the library. This is done by providing the YAXExceptionHandlingPolicies
parameter of the constructor of YAXSerializer
. This is an enum
with three possible values:
ThrowWarningsAndErrors
: Throws an exception at the time a 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.ThrowErrorsOnly
: 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 ParsingErrors
property.DoNotThrow
: Logs all kinds of exceptions (either labeled as warning or error), and does not throw any of them. The YAXCannotSerializeSelfReferentialTypes
and YAXObjectTypeMismatch
are two exceptions of this rule, but note that both of these exception classes happen only at serialization time, not during deserialization.
The third parameter of the constructor of the YAXSerializer
class specifies the default exception type (Warning
or Error
) for all exceptions that may happen (the default is Error
). We can alter this behavior for a special property of the class we want to serialize, by using the YAXErrorIfMissed
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 missing in the input XML. As an example, consider the ProgrammingLanguage
class below:
public class ProgrammingLanguage
{
public string LanguageName { get; set; }
public bool IsCaseSensitive { get; set; }
}
We require that the LanguageName
property be mandatory, but the IsCaseSensitive
property be optional. If the value corresponding to the IsCaseSensitive
property is missing in the XML, then we consider it as true
. For this purpose, we add attributes to the class definition, as shown below:
public class ProgrammingLanguage
{
[YAXErrorIfMissed(YAXExceptionTypes.Error)]
public string LanguageName { get; set; }
[YAXErrorIfMissed(YAXExceptionTypes.Warning, DefaultValue=true)]
public bool IsCaseSensitive { get; set; }
}
As seen in the above code snippet, we treat the absence of LanguageName
as an error, but we treat the absence of IsCaseSensitive
as a warning, and if this happens, we assign the default value of true
to this property. Consequently, we use the ThrowErrorsOnly
exception handling policy, and treat all other exceptions as warnings. The code below illustrates this while assuming that the input XML is stored in the string variable inputXML
.
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());
}
If the input XML is in the following format, then the deserialization finishes successfully:
<ProgrammingLanguage>
<LanguageName>C#</LanguageName>
<IsCaseSensitive>true</IsCaseSensitive>
</ProgrammingLanguage>
The following XML input leads to a warning which will be stored in the ParsingErrors
collection, but the value of IsCaseSensitive
will be set to true
.
<ProgrammingLanguage>
<LanguageName>C#</LanguageName>
</ProgrammingLanguage>
But, the following XML input leads to an exception being thrown, and therefore, the deserialization process fails.
<ProgrammingLanguage>
<IsCaseSensitive>true</IsCaseSensitive>
</ProgrammingLanguage>
Choosing the Fields to be Serialized
By default, YAXLib serializes only public
properties of the type that is specified. This behavior can be changed for any user-defined class through the YAXSerializableType
attribute. This attribute is applicable to classes and structures only, and sets some serialization options related to that special type. Note that existence of an attribute named YAXSerializableType
does not mean that the types that do not make use of this attribute are not serializable. In YAXLib, all types are merely serializable. This attribute helps us only in setting and overriding serialization options.
The YAXSerializableType
attribute has two named properties:
FieldsToSerialize
: Through this property, you can choose the type of members to be serialized. The default behavior is that public
properties are chosen to be serialized. There are three possible options. PublicPropertiesOnly
makes the serializer serialize only the public
properties (the default). AllFields
serializes public
and non-public properties and member variables. In order not to serialize some members, attribute them with the YAXDontSerialize
attribute. The third option is AttributedFieldsOnly
, which serializes only the members (public
or non-public properties or member variables) attributed as YAXSerializableField
.Options
: Through this property, you can choose whether or not to serialize null references. The default behavior, if not set otherwise by the serializer itself, is to serialize null references. Through this property, this behavior can be overloaded. There are two possible options that can be set: SerializeNullObjects
, which makes the serializer serialize all references including null references (the default), and DontSerializeNullObjects
, which makes the serializer not serialize null references. Setting the latter option will cause missing properties to be retrieved as null
. For a discussion on the benefits of not serializing null
objects, see the section Preserving Null-References Identity.
Defining Aliases for Enum Members
YAXLib enables you to define aliases for enum values through the YAXEnum
attribute. For example, consider that we have an enum of seasons defined as:
public enum Seasons
{
First, Second, Third, Fourth
}
Without applying any attributes to our enum, its members will be serialized as First
, Second
, and so on. But, if we change our enum in the following way:
public enum Seasons
{
[YAXEnum("Spring")]
First,
[YAXEnum("Summer")]
Second,
[YAXEnum(" Autumn or fall ")]
Third,
[YAXEnum("Winter")]
Fourth
}
and define and instantiate the SeasonsSample
class:
public class SeasonsSample
{
public Seasons[] ColdSeasons { get; set; }
}
...
SeasonsSample sample = new SeasonsSample()
{
ColdSeasons = new Seasons[] { Seasons.Third, Seasons.Fourth }
};
The result of serialization of the sample
object will be:
<SeasonsSample>
<ColdSeasons>
<Seasons>Autumn or fall</Seasons>
<Seasons>Winter</Seasons>
</ColdSeasons>
</SeasonsSample>
Since whitespace characters are valid in enum aliases, special care should be taken when a collection of enums is serialized serially. Since the space character is used by default as a separator in such collections, it is recommended that other characters be used as the separator instead of space. Also, enum members which are logically ored together are separated from each other by a comma. Therefore, selecting comma as a separator is not a wise decision either. The following code shows how to solve this problem, by selecting semicolon as the separator:
public class SeasonsSample
{
[YAXCollection(YAXCollectionSerializationTypes.Serially,
IsWhiteSpaceSeparator=false, SeparateBy=";")]
public Seasons[] ColdSeasons { get; set; }
}
The XML result which is unambiguously deserializable will be:
<SeasonsSample>
<ColdSeasons>Autumn or fall;Winter</ColdSeasons>
</SeasonsSample>
Serializing Multi-dimensional Arrays
YAXLib is capable of serializing and deserializing multi-dimensional and jagged arrays. For this reason, the serializer generates some meta-data about the dimensions of the array to guide the deserialization process. For example, consider the following class:
public class MultiDimArrays
{
public int[,] TheSquareMatrix { get; set; }
public int[][] TheTriangularMatrix { get; set; }
public static MultiDimArrays GetSampleInstance()
{
int[,] matrix = new int[3, 3];
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
matrix[i, j] = i + j + 1;
MultiDimArrays obj = new MultiDimArrays()
{
TheSquareMatrix = matrix,
TheTriangularMatrix = new int[3][]
{new int[] {1}, new int[] {1,2}, new int[] {1,2,3}}
};
return obj;
}
}
Serializing the object gained from the GetSampleInstance
static method yields the following XML result:
<MultiDimArrays xmlns:yaxlib="http://www.sinairv.com/yaxlib/">
<TheSquareMatrix yaxlib:dims="3,3">
<Int32>1</Int32>
<Int32>2</Int32>
<Int32>3</Int32>
<Int32>2</Int32>
<Int32>3</Int32>
<Int32>4</Int32>
<Int32>3</Int32>
<Int32>4</Int32>
<Int32>5</Int32>
</TheSquareMatrix>
<TheTriangularMatrix>
<Array1OfInt32>
<Int32>1</Int32>
</Array1OfInt32>
<Array1OfInt32>
<Int32>1</Int32>
<Int32>2</Int32>
</Array1OfInt32>
<Array1OfInt32>
<Int32>1</Int32>
<Int32>2</Int32>
<Int32>3</Int32>
</Array1OfInt32>
</TheTriangularMatrix>
</MultiDimArrays>
The above XML is successfully deserialized into an object with arrays with same dimensions. The only special thing to notice about the above XML result is the yaxlib:dims
attribute of the TheSquareMatrix
element. This attribute provides the needed meta-data to deserialize the matrix successfully.
Serializing Objects Through a Reference to Their Base-Class or Interface
Consider that you hold a collection of objects through a reference to their common interface or base-class and you need to serialize them in a way that their real type could be successfully determined during deserialization. This is what happens when you use a non-generic collection class; e.g., ArrayList
is merely a list of object
s. Successful serialization and deserialization of these kind of objects are possible in YAXLib, since it serializes meta-data to aid loading the real type of object at deserialization time. For example, consider the following class:
public class BaseClassRefSample
{
public IEnumerable RefToIEnumerable1 { get; set; }
public IEnumerable RefToIEnumerable2 { get; set; }
public List<object> ListOfObjects { get; set; }
public static BaseClassRefSample GetSampleInstance()
{
int[] someIntArray = new int[] { 1, 2, 3 };
string[] someStrArray = new string[] { "Hi", "Hello" };
List<object> lst = new List<object>();
lst.Add(7);
lst.Add(3.14);
lst.Add("Congrats");
lst.Add(StringSplitOptions.RemoveEmptyEntries);
return new BaseClassRefSample()
{
RefToIEnumerable1 = someIntArray,
RefToIEnumerable2 = someStrArray,
ListOfObjects = lst
};
}
}
The above class has three members. Two members are references to an IEnumerable
instance. In the GetSampleInstance
method, the first reference is referred to an array of integers, and the second one is assigned to an array of strings. So, two properties of similar appearance are referring to two different types of objects. The third property is a list of objects which is filled with an integer, a double number, a string, and an enum member. The XML result is the following:
<FreeSample xmlns:yaxlib="http://www.sinairv.com/yaxlib/">
<RefToIEnumerable1 yaxlib:realtype="System.Int32[]">
<Int32>1</Int32>
<Int32>2</Int32>
<Int32>3</Int32>
</RefToIEnumerable1>
<RefToIEnumerable2 yaxlib:realtype="System.String[]">
<String>Hi</String>
<String>Hello</String>
</RefToIEnumerable2>
<ListOfObjects>
<Object yaxlib:realtype="System.Int32">7</Object>
<Object yaxlib:realtype="System.Double">3.14</Object>
<Object yaxlib:realtype="System.String">Congrats</Object>
<Object yaxlib:realtype="System.StringSplitOptions">RemoveEmptyEntries</Object>
</ListOfObjects>
</FreeSample>
Note the use of the yaxlib:realtype
attributes that generate meta-data about the real type of the specified references. Through these meta-data, the deserializer will be able to load the true underlying types and assign values to them.
Preserving Null-References Identity
One of the primary objectives of a serialization library should be that the object before serialization must be member-wise equal to the object deserialized. This is violated sometimes if some of the object's properties are null
. For example, consider the following class containing only a List<>
:
public class CollectionOfItems
{
public List<int> TheCollection { get; set; }
}
And also, consider the following two unequal objects, namely obj1
and obj2
:
CollectionOfItems obj1 = new CollectionOfItems()
{
TheCollection = new List<int>()
};
CollectionOfItems obj2 = new CollectionOfItems()
{
TheCollection = null
};
As seen in the above code snippet, obj1
contains a non-null but empty List<>
, whereas the corresponding property in obj2
is null
. Now, the problem is that, they both produce the same XML output:
<CollectionOfItems>
<TheCollection />
</CollectionOfItems>
One solution is to prevent the serialization of null properties. Of course, this solution is not recommended if the structure of the XML output is important or it is going to be validated with some schema.
To accomplish this, the developer might specify the serialization options through the YAXSerializationOptions
enumeration. Currently, this enumeration has only two members:
DontSerializeNullObjects
which prevents serializing null propertiesSerializeNullObjects
which serializes all properties (the default)
If the developer sets the serialization option to DontSerializeNullObjects
through the constructor of YAXSerializer
, then we will see that the generated XML of obj1
and obj2
of the example above are different. More importantly, the objects before serialization and after deserialization are equal. This is the XML generated for obj2
with the option DontSerializeNullObjects
set:
<CollectionOfItems />
Formatting Data Items
YAXLib provides the YAXFormat
attribute with which developers can specify format strings for properties to be serialized. For example, if you want to serialize a double
variable with only three digits of precision after floating point, then you can use the F03
format string, through the YAXFormat
attribute.
This attribute also applies to collections of items, but it does not work for objects of type Dictionary
. Instead, you can use the YAXDictionary
attribute and specify the KeyFormatString
and ValueFormatString
parameters.
If the format string is not supported in the .NET Framework, then an exception of type YAXInvalidFormatProvided
is thrown/logged.
Special care should be taken while using format strings. Some data types can be serialized successfully with some format strings, but they might not be deserialized later. For example, you might accidentally use the F03G
format string for type double
.
As an example of the formatting capabilities of YAXLib, see the following class definition:
public class FormattingExample
{
public DateTime CreationDate { get; set; }
public DateTime ModificationDate { get; set; }
public double PI { get; set; }
public List<double> NaturalExp { get; set; }
public Dictionary<double, double> SomeLogarithmExample { get; set; }
}
Here's the code to create an instance of this class:
List<double> lstNE = new List<double>();
for (int i = 1; i <= 4; ++i)
lstNE.Add(Math.Exp(i));
Dictionary<double, double> dicLog = new Dictionary<double, double>();
for (double d = 1.5; d <= 10; d *= 2)
dicLog.Add(d, Math.Log(d));
return new FormattingExample()
{
CreationDate = new DateTime(2007, 3, 14),
ModificationDate = DateTime.Now,
PI = Math.PI,
NaturalExp = lstNE,
SomeLogarithmExample = dicLog
};
And, this is the XML output for our instance:
<FormattingExample>
<CreationDate>2007-03-14T00:00:00</CreationDate>
<ModificationDate>2009-04-13T17:40:11.56+04:30</ModificationDate>
<PI>3.1415926535897931</PI>
<NaturalExp>
<Double>2.7182818284590451</Double>
<Double>7.38905609893065</Double>
<Double>20.085536923187668</Double>
<Double>54.598150033144236</Double>
</NaturalExp>
<SomeLogarithmExample>
<KeyValuePairOfDoubleDouble>
<Key>1.5</Key>
<Value>0.40546510810816438</Value>
</KeyValuePairOfDoubleDouble>
<KeyValuePairOfDoubleDouble>
<Key>3</Key>
<Value>1.0986122886681098</Value>
</KeyValuePairOfDoubleDouble>
<KeyValuePairOfDoubleDouble>
<Key>6</Key>
<Value>1.791759469228055</Value>
</KeyValuePairOfDoubleDouble>
</SomeLogarithmExample>
</FormattingExample>
Now, let's add some formatting to the data items of our class definition:
public class FormattingExample
{
[YAXFormat("D")]
public DateTime CreationDate { get; set; }
[YAXFormat("d")]
public DateTime ModificationDate { get; set; }
[YAXFormat("F05")]
public double PI { get; set; }
[YAXFormat("F03")]
public List<double> NaturalExp { get; set; }
[YAXDictionary(KeyFormatString="F02", ValueFormatString="F05")]
public Dictionary<double, double> SomeLogarithmExample { get; set; }
}
The result of the XML serialization would be:
<FormattingExample>
<CreationDate>Wednesday, March 14, 2007</CreationDate>
<ModificationDate>4/13/2009</ModificationDate>
<PI>3.14159</PI>
<NaturalExp>
<Double>2.718</Double>
<Double>7.389</Double>
<Double>20.086</Double>
<Double>54.598</Double>
</NaturalExp>
<SomeLogarithmExample>
<KeyValuePairOfDoubleDouble>
<Key>1.50</Key>
<Value>0.40547</Value>
</KeyValuePairOfDoubleDouble>
<KeyValuePairOfDoubleDouble>
<Key>3.00</Key>
<Value>1.09861</Value>
</KeyValuePairOfDoubleDouble>
<KeyValuePairOfDoubleDouble>
<Key>6.00</Key>
<Value>1.79176</Value>
</KeyValuePairOfDoubleDouble>
</SomeLogarithmExample>
</FormattingExample>
Multi-Stage Deserialization
Some applications require multi-stage deserialization, in that later deserialized values override previous ones. An example of this technique can be observed in ASP.NET applications in which the values in the web.conf file in each directory override those settings previously defined in upper level directories. This technique can be accomplished using YAXLib through the SetDeserializationBaseObject
method. The steps are as follows:
- Deserialize the top-level XML file into an object, say
obj
. - Through the call
yaxSerializer.SetDeserializationBaseObject(obj)
, set the resulting object as the deserializer's base object for the next stage of deserialization. - Deserialize the next stage XML file into the same object,
obj
. - If there are any stages (XML files) remaining, go back to step 2.
The Problem with Classes without a Default Constructor
YAXLib needs that the classes used with it do have a default constructor. But, there are many classes already defined in the .NET Framework that do not have a default constructor (e.g., the System.Drawing.Color struct
). To overcome this problem, we can create properties of some other types (e.g., string
) in which the get
and set
methods convert between the two types (e.g., convert string
to Color
and vice versa). Here's an example that uses a property of type string
instead of a property of type Color
:
public class ColorExample
{
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("#"))
value = value.Substring(1);
int n;
if (Int32.TryParse(value,
System.Globalization.NumberStyles.HexNumber, null, out n))
{
m_color = Color.FromArgb(n);
}
}
}
}
Here is the XML generated by serializing an object of the above class:
<SelfReferentialExample>
<TheColor>#FF0000FF</TheColor>
</SelfReferentialExample>
The Problem with Self-Referential Classes
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 YAXCannotSerializeSelfReferentialTypes
exception is thrown if such classes are encountered during the serialization process. This is one of the few YAXLib exception classes that cannot be turned off even if the exception-handling policy is set to DoNotThrow
. So, it is recommended that you always put your serialization code within try
blocks. To overcome this problem, the same technique for solving The Problem with Classes without a Default Constructor can be used.
YAXLib Attributes
In this section, we list and describe all attribute classes provided in the YAXLib library. Note that in the descriptions that follow, by the term field, we mean a member variable which could be either public
or non-public.
YAXSerializeAs
: Defines an alias for the field, property, class, or struct under which it will be serialized. This attribute is applicable to fields, properties, classes, and structs.YAXDontSerialize
: Prevents serialization of some field or property. This attribute is applicable to fields and properties.YAXAttributeForClass
: Makes a property to appear as an attribute for the enclosing class (i.e., the parent element), if possible. This attribute is applicable to fields and properties.YAXAttributeFor
: Makes a field or property to appear as an attribute for another element, if possible. This attribute is applicable to fields and properties.YAXElementFor
: Makes a property or field to appear as a child element for another element. This attribute is applicable to fields and properties.YAXCollection
: Controls the serialization of collection instances. This attribute is applicable to fields and properties. For more details and examples, see the section: Serializing Objects of Collection Classes.YAXDictionary
: Controls the serialization of objects of type Dictionary
. This attribute is applicable to fields and properties.YAXFormat
: Specifies the format string provided for serializing data. The format string is the parameter passed to the ToString
method. If this attribute is applied to collection classes, the format, therefore, is applied to the collection members. This attribute is applicable to fields and properties.YAXEnum
: Specifies an alias for an enum member. This attribute is applicable to enum members.YAXSerializableType
: Sets or overrides the default serialization behaviors. This attribute is optional, and is applicable to classes and structures.YAXSerializableField
: Add this attribute to properties or fields which you wish to be serialized, when the enclosing class uses the YAXSerializableType
attribute in which FieldsToSerialize
has been set to AttributedFieldsOnly
. This attribute is applicable to fields and properties.YAXErrorIfMissed
: Specifies the behavior of the deserialization method if the element/attribute corresponding to this property is missing in the XML input. This attribute is applicable to fields and properties.YAXNotCollection
: Specifies that a particular class, or a particular property or variable type, that is driven from IEnumerable
should not be treated as a collection class/object. This attribute is applicable to fields, properties, classes, and structs.YAXComment
: Creates a comment node per each line of the comment string provided. This attribute is applicable to classes, structures, fields, and properties.
YAXLib Exception Classes
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 the YAXException
class.
- Exceptions raised during serialization:
YAXBadLocationException
: Raised when the location of the serialization specified cannot be created or cannot be read from.YAXAttributeAlreadyExistsException
: Raised when trying to serialize an attribute where another attribute with the same name already exists.YAXCannotSerializeSelfReferentialTypes
: Raised when trying to serialize self-referential types. This exception cannot be turned off.YAXObjectTypeMismatch
: Raised when the object provided for serialization is not of the type provided for the serializer. This exception cannot be turned off.YAXInvalidFormatProvided
: Raised when an object cannot be formatted with the format string provided.
- Exceptions raised during deserialization:
YAXAttributeMissingException
: Raised when the attribute corresponding to some property is not present in the given XML file.YAXElementMissingException
: Raised when the element corresponding to some property is not present in the given XML file.YAXBadlyFormedInput
: Raised when the value provided for some property in the XML input cannot be converted to the type of the property.YAXPropertyCannotBeAssignedTo
: Raised when the value provided for some property in the XML input cannot be assigned to the property.YAXCannotAddObjectToCollection
: Raised when some member of the collection in the input XML cannot be added to a collection object.YAXDefaultValueCannotBeAssigned
: Raised when the default value specified by YAXErrorIfMissedAttribute
could not be assigned to the property.YAXBadlyFormedXML
: Raised when the XML input does not follow standard XML formatting rules.
When Not to Use YAXLib
The emphasis of YAXLib on the structure and 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 a 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 not going to be edited by a human user later, and your objects do not use collection classes such as Dictionary<,>
, then it might be better to use the XmlSerializer
class of the .NET Framework, because your code would be more portable.
History
- May 17, 2010: YAXLib is now hosted on CodePlex.
- April 16, 2010: YAXLib 2.0, with a lot more features.
- December 22, 2009: Some bug-fixes, and added a download link to an SVN repository so that every bug-fix does not require an update to the CodeProject article.
- April 13, 2009: YAXLib 1.1.
- March 13, 2009: First version.
For more detailed information, see the change-log file.