Fork me on GitHub

Programming, Internet & more

C# How to force decimal precision in xml serialization

Recently, I’ve tried to serialize some xml in c# and stumbled accross the problem that I had to force the precision scale of decimal values. By default the XmlSerializer uses the exact value of the underlying decimal value while serializing to xml.

That means, if you assign 2 to a decimal value your xml will look like

<myvalue>2</myvalue>

But if you assign 2.00 to a decimal value it will look like

<myvalue>2.00</myvalue>

One solution could be to always use the Math.Round() function to round your decimal values. But this will lead to a lot of unnecessary and unmaintainable code.
Instead I wanted a solution that automatically cares about the decimal scale during xml serialization.

XmlSerializer extension

The solution I’ve come up with uses an extension method for the XmlSerializer class.
This method iterates through all public properties, looks for decimal values and applies the target precision on the decimal value. It also works for fairly complex xml serialization trees with nested objects and lists.

using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace Slackspace.Serializer
{
    public static class XmlSerializerExtensions
    {
        // the target format of the decimal precision, change to your needs
        private const string NumberFormat = "0.00";

        public static void SerializeWithDecimalFormatting(this XmlSerializer serializer, Stream stream, object o)
        {
            IteratePropertiesRecursively(o);
           
            serializer.Serialize(stream, o);
        }

        private static void IteratePropertiesRecursively(object o)
        {
            if (o == null)
                return;

            var type = o.GetType();

            var properties = type.GetProperties();

            // enumerate the properties of the type
            foreach (var property in properties)
            {
                var propertyType = property.PropertyType;

                // if property is a generic list
                if (propertyType.Name == "List`1")
                {
                    var val = property.GetValue(o, null);
                    var elements = val as IList;

                    if (elements != null)
                    {
                        // then iterate through all elements
                        foreach (var item in elements)
                        {
                            IteratePropertiesRecursively(item);
                        }
                    }
                }
                else if (propertyType == typeof (decimal))
                {
                    // check if there is a property with name XXXSpecified, this is the case if we have a type of decimal?
                    var specifiedPropertyName = string.Format("{0}Specified", property.Name);
                    var isSpecifiedProperty = type.GetProperty(specifiedPropertyName);
                    if (isSpecifiedProperty != null)
                    {
                        // only apply the format if the value of XXXSpecified is true, otherwise we will get a nullRef exception for decimal? types
                        var isSpecifiedPropertyValue = isSpecifiedProperty.GetValue(o, null) as bool?;
                        if (isSpecifiedPropertyValue == true)
                        {
                            FormatDecimal(property, o);
                        }
                    }
                    else
                    {
                        // if there is no property with name XXXSpecified, we can safely format the decimal
                        FormatDecimal(property, o);
                    }
                }
                else
                {
                    // if property is a XML class (contains XML in name) iterate through properties of this class
                    if (propertyType.Name.ToLower().Contains("xml") && propertyType.IsClass)
                    {
                        IteratePropertiesRecursively(property.GetValue(o));
                    }
                }
            }
        }

        private static void FormatDecimal(PropertyInfo p, object o)
        {
            // if property is decimal, apply correct number format
            var value = (decimal) p.GetValue(o, null);
            var formattedString = value.ToString(NumberFormat, CultureInfo.InvariantCulture);
            p.SetValue(o, decimal.Parse(formattedString), null);
        }

    }
}

Note that the decimal precision is fixed in the NumberFormat field and is applied for all decimal values in the xml.

Usage

To use the serializer extension you can simply call the new extension method instead of the default one:

using (var ms = new MemoryStream())
{
    var xmlObject = MyObjectXml();
   
    var serializer = new XmlSerializer(typeof(MyObjectXml));
    serializer.Serialize(ms, xmlObject);
}

What about Nested Objects?

One word about nested objects in your xml graph. If you use nested objects then you must name your classes with Xml at the end (or exchange the xml string in the code) in order to make it work with the serializer extension, otherwise the properties in these classes will not be inspected and the decimal precision scale cannot be applied.

Example for nested objects:

using System.Collections.Generic;
using System.Xml.Serialization;

namespace Slackspace.Serializer.Model
{
    public class MyObjectXml
    {
        [XmlAttribute(AttributeName = "id")]
        public long Id { get; set; }

        [XmlArray(ElementName = "students")]
        [XmlArrayItem(ElementName = "student")]
        public List<StudentXml> Students { get; set; }

    }

    public class StudentXml
    {
        [XmlAttribute(AttributeName = "averageGrade")]
        public decimal AverageGrade { get; set; }
    }
}

Are nullable decimals supported?

When you’re using nullable decimals in your xml classes then you can just use the standard model with the Specified property. In order of completeness here is an example that makes use of a nullable decimal value.

using System.Xml.Serialization;

namespace Slackspace.Serializer.Model
{
    public class MyXmlObject
    {
        [XmlAttribute(AttributeName = "price")]
        public decimal XmlPrice { get { return Price.Value; } set { Price = value; } }  

        [XmlIgnore]
        public decimal? Price { get; set; }

        public bool XmlPriceSpecified { get { return Price.HasValue; } }
    }
}

Summary

If you want to force the decimal precision during xml serialization the best way I found was to make use of the extension method concept in c#. The extension of the xml serializer makes it really easy to don’t care about the decimal scale at all and do all the hard work during the serialization state.

Category: c#, programming, tutorials
-->

Post a Comment

Your email is kept private. Required fields are marked *

*
*