![]() |
Languages »
XML »
Serializing
Intermediate
License: The Code Project Open License (CPOL)
Yet Another XML Serialization Library for the .NET FrameworkBy Sina IravanianAn XML serialization library which lets developers design the XML file structure, and select the exception handling policy. This library also supports serializing most of the collection classes such as the Dictionary generic class. |
C#, XML.NET 3.0, .NET 3.5, Architect, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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:
Dictionary<,>) or properties of type IEnumerable<>.The features of YAXLib that solve the above problems are:
Dictionary<,>, so that the developer has full control on the appearance of the generated XML.List<>, HashSet<>, Dictionary<,>, properties of type IEnumerable<>, and many others). For more information, see the section: Known Issues / Shortcomings (of course, the XmlSerializer class also supports many of these collection types).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:
// 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);
After executing the above code, the variable someS will contain:tring
<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>
Make 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 by 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>
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, // square meters
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 two of them defined in the library. One is called Recursive, and the other is called 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 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.
Let's improve our example a little more by adding a dictionary of, say, PossibleItems, 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, // square meters
Items = new PossibleItems[] { PossibleItems.Item3,
PossibleItems.Item6,
PossibleItems.Item9,
PossibleItems.Item12 },
ItemQuantitiesDic = dicItems
};
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>
<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; }
//[YAXCollection(YAXCollectionSerializationTypes.Recursive,
// EachElementName = "ItemInfo")]
[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.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, // 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 }
};
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>
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. Note that this attribute is applicable to classes only. For example, see the modified version of our warehouse below:
[YAXComment("All the fields are mandatory, please fill all of them")]
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; }
}
[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; }
public Person Owner { get; set; }
}
and the generated XML:
<Warehouse Name="Foo Warehousing Ltd.">
<!--General information about a warehouse-->
<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">
<!--All the fields are mandatory, please fill all of them-->
<Identification Name="John" Family="Doe" />
<Age>50</Age>
</Owner>
</Warehouse>
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)
// means that the XML input has been deserialized successfully
else
// the XML input cannot be deserialized successfully
}
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 YAXExceptions 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 the 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 missed 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>
<!-- <IsCaseSensitive>true</IsCaseSensitive> -->
</ProgrammingLanguage>
But, the following XML input leads to an exception being thrown, and therefore, the deserialization process fails.
<ProgrammingLanguage>
<!-- <LanguageName>C#</LanguageName> -->
<IsCaseSensitive>true</IsCaseSensitive>
</ProgrammingLanguage>
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 properties.SerializeNullObjects 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 />
YAXLib provides the YAXFormat attribute with which the 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 attributes 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, one 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 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>
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("#")) // 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);
}
}
}
}
Here is the XML generated by serializing an object of the above class:
<SelfReferentialExample>
<TheColor>#FF0000FF</TheColor>
</SelfReferentialExample>
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.
In this section, we list and describe all attribute classes provided in the YAXLib library.
YAXSerializeAs: Defines an alias for the property or class under which it will be serialized. This attribute is applicable to properties and classes only.YAXDontSerialize: Prevents serialization of some property. This attribute is applicable to properties only.YAXAttributeForClass: 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.YAXAttributeFor: Makes a property to appear as an attribute for another element, if possible. This attribute is applicable to properties only.YAXElementFor: Makes a property to appear as a child element for another element. This attribute is applicable to properties only.YAXCollection: Controls the serialization of collection instances. This attribute is applicable to properties only. 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 properties only.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 properties only.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 properties only.YAXNotCollection: Specifies that a particular class that is derived from IEnumerable should not be treated as a collection class. This attribute is applicable to properties only. See the MoreComplexExample class in the demo application.YAXComment: Creates a comment node for the main XML element. This attribute is applicable to classes only.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.
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.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 the YAXErrorIfMissedAttribute could not be assigned to the property.YAXBadlyFormedXML: Raised when the XML input does not follow standard XML formatting rules.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.
YAXLib assumes that the public properties of the objects are all that is needed to be serialized. Here, you can find another work that takes into account private fields also.
YAXLib does not support non-generic collection classes; try to use the corresponding generic collection types instead (e.g., use Dictionary<,> instead of Hashtable).
The behavior of the library is undefined when it is working on collections of heterogeneous members (e.g., a List<object> that contains integers and strings and other types of objects at the same time).
YAXLib can not define aliases for enum items (maybe a future work).
For more detailed information, see the change-log file.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 28 Apr 2009 Editor: Smitha Vijayan |
Copyright 2009 by Sina Iravanian Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |