Click here to Skip to main content
6,822,613 members and growing! (15,309 online)
Email Password   helpLost your password?
Languages » XML » Serializing     Intermediate License: The Code Project Open License (CPOL)

Yet Another XML Serialization Library for the .NET Framework

By Sina Iravanian

An 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.NET3.0, .NET3.5, Architect, Dev
Revision:9 (See All)
Posted:13 Mar 2009
Updated:25 Dec 2009
Views:22,423
Bookmarked:105 times
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
55 votes for this article.
Popularity: 8.50 Rating: 4.88 out of 5

1

2

3
5 votes, 9.1%
4
50 votes, 90.9%
5

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 config 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 serializing most of the collection types (including arrays, 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).
  • 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:

// 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 someString 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>

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 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, // 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.

Serializing Objects of Type Dictionary

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.

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, // 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>

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. 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>

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)
        // 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 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>
  <!-- <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>

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 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 />

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 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>

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("#")) // 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>

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.

  • 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 both properties and classes. 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.

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
    • 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 the 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.

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.

Known Issues / Shortcomings

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).

History

  • December 22, 2009: Some bug-fixes, and adding 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sina Iravanian


Member
I received my BSc in Computer Software Engineering from Iran University of Science and Technology. Currently I study MS in Computer Science at Sharif University of Technology.
I am proficient with C# (Desktop Applications) and C++, and have programming experience with Java, and VBA.
Occupation: Software Developer
Location: Iran (Islamic Republic Of) Iran (Islamic Republic Of)

Other popular XML articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 87 (Total in Forum: 87) (Refresh)FirstPrevNext
GeneralIs this possible? Pinmemberdonovan.solms21:53 4 Feb '10  
GeneralRe: Is this possible? PinmemberSina Iravanian3:38 5 Feb '10  
QuestionPossible Bug? [modified] Pinmemberdonovan.solms21:44 27 Jan '10  
AnswerRe: Possible Bug? PinmemberSina Iravanian22:15 27 Jan '10  
GeneralRe: Possible Bug? Pinmemberdonovan.solms22:48 27 Jan '10  
GeneralRe: Possible Bug? PinmemberSina Iravanian4:33 28 Jan '10  
GeneralRe: Possible Bug? Pinmemberdonovan.solms4:40 28 Jan '10  
GeneralNesting Pinmemberrobert austin3:35 6 Jan '10  
GeneralRe: Nesting PinmemberSina Iravanian3:15 10 Jan '10  
GeneralIts good PinmemberBhasker Kandpal22:29 27 Dec '09  
GeneralRe: Its good PinmemberSina Iravanian21:40 28 Dec '09  
GeneralReally Good! PinmemberAnt21008:22 25 Dec '09  
GeneralRe: Really Good! PinmemberSina Iravanian12:28 26 Dec '09  
QuestionAbout Array serialize Pinmemberxinggg2222:03 21 Dec '09  
AnswerRe: About Array serialize PinmemberSina Iravanian0:09 22 Dec '09  
GeneralRe: About Array serialize Pinmemberxinggg2215:11 22 Dec '09  
GeneralRe: About Array serialize PinmemberSina Iravanian21:42 22 Dec '09  
QuestionRe: About Array serialize Pinmemberxinggg223:04 26 Dec '09  
GeneralProperty serialization Pinmembergonenb9:56 16 Nov '09  
GeneralRe: Property serialization PinmemberSina Iravanian20:17 16 Nov '09  
GeneralRe: Property serialization Pinmembergonenb20:46 16 Nov '09  
GeneralRe: Property serialization PinmemberSina Iravanian0:05 17 Nov '09  
GeneralRe: Property serialization Pinmembergonenb3:58 17 Nov '09  
GeneralRe: Property serialization PinmemberSina Iravanian19:48 17 Nov '09  
GeneralRe: Property serialization Pinmembergonenb21:43 18 Nov '09  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.

PermaLink | Privacy | Terms of Use
Last Updated: 25 Dec 2009
Editor: Smitha Vijayan
Copyright 2009 by Sina Iravanian
Everything else Copyright © CodeProject, 1999-2010
Web17 | Advertise on the Code Project