4

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?

Mykhailo Seniutovych
  • 3,527
  • 4
  • 28
  • 50

2 Answers2

5

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);
steve16351
  • 5,372
  • 2
  • 16
  • 29
  • Thanks for the reply! If I override it like this `attributeOverrides.Add(typeof(Person), "Name", new XmlAttributes() { XmlDefaultValue = null });` then all XmlAttributes will be overriden, in my example `[System.Xml.Serialization.XmlAttribute()]` will no longer be there. Is there a way to override only `DefaultValue` and leave all other attributes intact? – Mykhailo Seniutovych Jul 17 '18 at 14:11
  • 1
    Not as far as I can see, though you can manually also set the other attributes for each property you are changing to restore them. I added some code that can automate the creation of the `XmlAttributeOverrides` to copy existing and then customise, in my example assuming you want to remove the `DefaultValueAttribute` on all properties in a given class. – steve16351 Jul 17 '18 at 15:29
1

I dont have enough reputation to comment on steve16351's answer. but I feel I improved upon his code a little bit.

My use case involved an XML schema file generated via XSD.exe provided by Microsoft, and a class was then generated from the schema file. So the class had all the DefaultValue tags on it from the schema. There was also many nested types created. So to make my code succinct, I modified the 'Generate' method to get the overrides for the top-level class, and all the ones beneath it. Then everything is returned in a single XmlAttributesOverrides object.

static XmlAttributeOverrideGenerator()
{
    _overrides = Generate(typeof(T), new XmlAttributeOverrides());
}

private static XmlAttributeOverrides Generate(Type targetType, XmlAttributeOverrides Overrides)
{
    foreach (var property in targetType.GetProperties())
    {
        if (property.CustomAttributes.Any( CA => CA.AttributeType == typeof(DefaultValueAttribute)))
        {
            XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
            Overrides.Add(targetType, property.Name, propertyAttributes);
        }
        else
        {
            Type propType = property.PropertyType;
            if (propType.IsClass && !propType.IsValueType && !propType.IsEnum && !propType.IsPrimitive && !propType.IsSealed)  // Check if this is a custom class
            {
                //If this is a nested class or other class type, generate its overrides as well
                Generate(propType, Overrides);
            }
        }
    }

    return Overrides;
}
RFBomb
  • 51
  • 6