Rich Bennema
Rich Bennema

Reputation: 10345

How to get XmlSerializer to ignore all members of a certain type?

I want to deserialize XML into the following class:

public partial class Delivery
{
    public System.Nullable<System.DateTime> sentDate { get; set; }
    public System.Nullable<System.DateTime> receivedDate { get; set; }
    public System.Nullable<System.DateTime> responseDueDate { get; set; }
}

However, the dates in the XML are not in an XmlSerializer friendly format. Based on answers to mulitple questions, I added this class:

public partial class DateSafeDelivery : Delivery
{
    [XmlElement("sentDate")]
    public string sentDateString
    {
        internal get { return sentDate.HasValue ? XmlConvert.ToString(sentDate.Value) : null; }
        set { sentDate = DateTime.Parse(value); }
    }
    [XmlElement("receivedDate")]
    public string receivedDateString
    {
        internal get { return receivedDate.HasValue ? XmlConvert.ToString(receivedDate.Value) : null; }
        set { receivedDate = DateTime.Parse(value); }
    }
    [XmlElement("responseDueDate")]
    public string responseDueDateString
    {
        internal get { return responseDueDate.HasValue ? XmlConvert.ToString(responseDueDate.Value) : null; }
        set { responseDueDate = DateTime.Parse(value); }
    }
}

I then configure my overrides:

private static XmlAttributeOverrides GetOverrides()
{
    var overrides = new XmlAttributeOverrides();
    var attributes = new XmlAttributes();
    attributes.XmlElements.Add(new XmlElementAttribute(typeof(DateSafeDelivery)));
    overrides.Add(typeof(MyParent), "Delivery", attributes);
    var ignore = new XmlAttributes { XmlIgnore = true };
    overrides.Add(typeof(DateTime?), ignore);
    return overrides;
}

This results in the following expection:

Message=The string '2010-06-12T00:00:00 -05:00' is not a valid AllXsd value.
Source=System.Xml.ReaderWriter
StackTrace:
    at System.Xml.Schema.XsdDateTime..ctor(String text, XsdDateTimeFlags kinds)
    at System.Xml.XmlConvert.ToDateTime(String s, XmlDateTimeSerializationMode dateTimeOption)
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderDeserializedAudit.Read1_NullableOfDateTime(Boolean checkType)
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderDeserializedAudit.Read15_DateSafeDelivery(Boolean isNullable, Boolean checkType)
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderDeserializedAudit.Read16_MyParent(Boolean isNullable, Boolean checkType)

So DateSafeDelivery is being used, but the XmlIgnore for the dates is being, well, ignored.

It will work, if I switch:

    overrides.Add(typeof(DateTime?), ignore);

with:

    new Dictionary<string, Type>()
    {
        { "sentDate", typeof(Delivery) },
        { "receivedDate", typeof(Delivery) },
        { "responseDueDate", typeof(Delivery) },
    }
        .ToList()
        .ForEach(t1 => overrides.Add(t1.Value, t1.Key, ignore));

And that's fine for one class and three properties. But I have 14 classes with a total of three dozen date properties. I know I have to add overrides for the 14 classes, but is there a way to get the serializer to ignore all DateTime properties?

I thought XmlAttributeOverrides.Add Method (Type, XmlAttributes) would do it. But it isn't working. Why? What is this method for? What does it do?

Upvotes: 3

Views: 2106

Answers (1)

dbc
dbc

Reputation: 116970

XmlAttributeOverrides.Add(Type, XmlAttributes) is designed to add an XML override attribute to the type itself, rather than to all properties returning values of that type. E.g. if you wanted to add an [XmlRoot("OverrideName")] attribute to DateSafeDelivery, you could do something like:

overrides.Add(typeof(DateSafeDelivery),
    new XmlAttributes { XmlRoot = new XmlRootAttribute("OverrideName") });

There is no dynamic override attribute to ignore all properties returning a given type because there is no static XML serialization attribute that can suppress serialization of all properties of a given type. The following does not even compile, because [XmlIgnore] can only be applied to a property or field:

[XmlIgnore] public class IgnoreAllInstancesOfMe { } // Fails to compile.

(As to why Microsoft did not implement support for [XmlIgnore] applied to a type - you would need to ask them.)

Thus you will need to introduce an extension method like the following:

public static partial class XmlAttributeOverridesExtensions
{
    public static XmlAttributeOverrides IgnorePropertiesOfType(this XmlAttributeOverrides overrides, Type declaringType, Type propertyType)
    {
        return overrides.IgnorePropertiesOfType(declaringType, propertyType, new HashSet<Type>());
    }

    public static XmlAttributeOverrides IgnorePropertiesOfType(this XmlAttributeOverrides overrides, Type declaringType, Type propertyType, HashSet<Type> completedTypes)
    {
        if (overrides == null || declaringType == null || propertyType == null || completedTypes == null)
            throw new ArgumentNullException();
        XmlAttributes attributes = null;
        for (; declaringType != null && declaringType != typeof(object); declaringType = declaringType.BaseType)
        {
            // Avoid duplicate overrides.
            if (!completedTypes.Add(declaringType))
                break;
            foreach (var property in declaringType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                if (property.PropertyType == propertyType || Nullable.GetUnderlyingType(property.PropertyType) == propertyType)
                {
                    attributes = attributes ?? new XmlAttributes { XmlIgnore = true };
                    overrides.Add(declaringType, property.Name, attributes);
                }
            }
        }
        return overrides;
    }
}

And do:

    private static XmlAttributeOverrides GetOverrides()
    {
        var overrides = new XmlAttributeOverrides();

        var attributes = new XmlAttributes();
        attributes.XmlElements.Add(new XmlElementAttribute(typeof(DateSafeDelivery)));
        overrides.Add(typeof(MyParent), "Delivery", attributes);

        // Ignore all DateTime properties in DateSafeDelivery
        var completed = new HashSet<Type>();
        overrides.IgnorePropertiesOfType(typeof(DateSafeDelivery), typeof(DateTime), completed);
        // Add the other 14 types as required

        return overrides;
    }

Please also note that the DateString properties on DateSafeDelivery must have public get and set methods, e.g.:

public partial class DateSafeDelivery : Delivery
{
    [XmlElement("sentDate")]
    public string sentDateString
    {
        get { return sentDate.HasValue ? XmlConvert.ToString(sentDate.Value, XmlDateTimeSerializationMode.Utc) : null; }
        set { sentDate = DateTime.Parse(value); }
    }

XmlSerializer cannot serialize a property that is not fully public.

Incidentally, please note you must statically cache any XmlSerializer constructed with overrides to avoid a severe memory leak, as explained in this answer.

Upvotes: 1

Related Questions