Search code examples
c#xmlattributesxmlserializerxmlwriter

How to serialize properties with DefaultValueAttribute using XmlSerializer?


I am using XmlSerializer to serialize C# objects to XML. I have DefaultValueAttribute on some of the properties of the classes I am trying to serialize, when I try to serialize them it seems that XmlSerializer does not include value in xml if it equals default value. Look at this example:

using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace Test
{
    public class Person
    {
        [System.Xml.Serialization.XmlAttribute()]
        [System.ComponentModel.DefaultValue("John")]
        public string Name { get; set; }
    }

    public static class Test
    {
        public static void Main()
        {
            var serializer = new XmlSerializer(typeof(Person));
            var person = new Person { Name = "John" };

            using (var sw = new StringWriter())
            {
                using (var writer = XmlWriter.Create(sw))
                {
                    serializer.Serialize(writer, person);
                    var xml = sw.ToString();
                }
            }
        }
    }
}

It will produce the following xml (notice name attribute is not available):

<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

I cannot modify the source code of the classes so I CANNOT remove DefaultValueAttribute. Is there a way to make XmlSerializer serialize this properties without changing the source code?


Solution

  • You can do it by passing in an XmlAttributeOverrides instance to the XmlSerializer when you create it as below. You can either change the default value to something else using this, or set it to null to effectively remove it.

    XmlAttributeOverrides attributeOverrides = new XmlAttributeOverrides();
    
    var attributes = new XmlAttributes()
    {
        XmlDefaultValue = null,
        XmlAttribute = new XmlAttributeAttribute()
    };
    
    attributeOverrides.Add(typeof(Person), "Name", attributes);
    
    var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
    var person = new Person { Name = "John" };
    
    using (var sw = new StringWriter())
    {
        using (var writer = XmlWriter.Create(sw))
        {
            serializer.Serialize(writer, person);
            var xml = sw.ToString();
        }
    }
    

    Update: the above means you have to provide other unrelated attributes again on each property you are changing. This is a bit of a chore if you have a lot of properties and just want to remove default for all of them. The class below can be used to preserve other custom attributes while only removing one type of attribute. It can be further extended if required to only do it for certain properties etc.

    public class XmlAttributeOverrideGenerator<T>
    {
        private static XmlAttributeOverrides _overrides;
        private static Type[] _ignoreAttributes = new Type[] { typeof(DefaultValueAttribute) };
    
        static XmlAttributeOverrideGenerator()
        {
            _overrides = Generate();
        }
    
        public static XmlAttributeOverrides Get()
        {
            return _overrides;
        }
    
        private static XmlAttributeOverrides Generate()
        {
            var xmlAttributeOverrides = new XmlAttributeOverrides();
    
            Type targetType = typeof(T);
            foreach (var property in targetType.GetProperties())
            {
                XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
                xmlAttributeOverrides.Add(targetType, property.Name, propertyAttributes);
            }
    
            return xmlAttributeOverrides;
        }
    
        public class CustomAttribProvider : ICustomAttributeProvider
        {
            private PropertyInfo _prop = null;
            private Type[] _ignoreTypes = null;            
    
            public CustomAttribProvider(PropertyInfo property, params Type[] ignoreTypes)
            {
                _ignoreTypes = ignoreTypes;
                _prop = property;
            }
    
            public object[] GetCustomAttributes(bool inherit)
            {
                var attribs = _prop.GetCustomAttributes(inherit);
                if (_ignoreTypes == null) return attribs;
                return attribs.Where(attrib => IsAllowedType(attrib)).ToArray();
            }
    
            private bool IsAllowedType(object attribute)
            {
                if (_ignoreTypes == null) return true;
                foreach (Type type in _ignoreTypes)
                    if (attribute.GetType() == type)
                        return false;
    
                return true;
            }
    
            public object[] GetCustomAttributes(Type attributeType, bool inherit)
            {
                throw new NotImplementedException();
            }
    
            public bool IsDefined(Type attributeType, bool inherit)
            {
                throw new NotImplementedException();
            }
        }
    }
    

    Usage:

    XmlAttributeOverrides attributeOverrides = XmlAttributeOverrideGenerator<Person>.Get();            
    var serializer = new XmlSerializer(typeof(Person), attributeOverrides);