1

I would like to be able to XML serialize a subclass, but exclude its base class from the serialization.

Here's my class (configured for DataObjects.NET):

[Serializable]
[HierarchyRoot]
public class MyClass: Entity
{
    [Field, Key] public int Id { get; private set; }
    [Field(Length = 20)] public string Name { get; set; }
}

Base class Entity cannot be serialized because it does not have a parameterless constructor.

I won't be trying to reconstitute the object by deserializing (no Liskov issues). I just want the data from the subclass in XML. IXMLSerializable is not an attractive option, as my real classes are numerous and much more complex.

Is there any way to tell XmlSerializer to ignore the base class?

Qodex
  • 299
  • 3
  • 12

1 Answers1

2

First off, the base class doesn't need a parameterless constructor as long as the derived class, MyClass, has one. Thus the following derived class can be round-tripped from and to XML:

public class BaseClassWithNoParameterlessConstructor
{
    public BaseClassWithNoParameterlessConstructor(string value)
    {
        this.Value = value;
    }

    public string Value { get; set; }
}

public class DerivedClassWithAParameterlessConstructor : BaseClassWithNoParameterlessConstructor
{
    public DerivedClassWithAParameterlessConstructor()
        : this(string.Empty) // Some plausible default value
    {
    }

    public DerivedClassWithAParameterlessConstructor(string value)
        : base(value)
    {
    }

    public string Name { get; set; }
}

However, the presence of the property public int Id { get; private set; } will cause the XmlSerializer constructor to throw an exception, because Id cannot be set publicly. You need to mark it with [XmlIgnore] or make the setter public. Or if you really don't want to make the setter public, and never intend to deserialize your xml, you could do something evil like:

    [XmlIgnore]
    public int Id { get; private set; }

    [XmlElement("Id")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public int DummyIdWIthFakeSetter { get { return Id; } set { /* DO NOTHING */ } }

Secondly, to suppress a specific property in a base class, named XXX for instance, you can introduce a method named bool ShouldSerializeXXX() in your derived class, and make it return false. This will suppress output of that property, but will need to be done property-by-property. See here for details.

If you want to ignore all properties declared in base classes wholesale, you can construct an XmlSerializer with given XmlAttributeOverrides that specify base class property names to ignore, as is shown here. However, there are some issues of which to be aware:

  1. You can only add an XmlAttributes override to a property in the type in which the property is actually declared. Doing so will apply to that property in that type and all derived types. This will do what you want - but maybe more than you want. If you try to override the property just in a derived type, the override is ignored. I'm not sure this is documented, but I have found it to be true. Thus if you were serializing an object graph containing both base and derived classes, and wanted to serialize base class properties when "standalone" but not when subclassed, this technique would produce bad results. But in your case it should be OK because your base classes cannot be serialized standalone anyway.

  2. You must explicitly cache your XmlSerializer in a hash table to avoid memory leaks, as is explained here.

Thus, if you know all the base types whose properties should be ignored, you can use the following to manufacture a serializer that serializes subclasses and ignores base class properties:

public static class XmlSerializationIgnoreOverrideCreator<T>
{
    static Dictionary<HashSet<Type>, XmlSerializer> table = new Dictionary<HashSet<Type>, XmlSerializer>(HashSet<Type>.CreateSetComparer());

    public static XmlSerializer CreateOverrideSerializer()
    {
        return CreateOverrideSerializer(new Type[] { typeof(T).BaseType });
    }

    public static XmlSerializer CreateOverrideSerializer(IEnumerable<Type> typesWithPropertiesToIgnore)
    {
        var set = new HashSet<Type>(typesWithPropertiesToIgnore);
        if (set.Count == 0)
            return new XmlSerializer(typeof(T));
        lock (table)
        {
            XmlSerializer serializer;
            if (table.TryGetValue(set, out serializer))
                return serializer;

            var xOver = new XmlAttributeOverrides();

            foreach (var type in set)
            {
                IgnoreAllProperties(type, xOver);
            }

            table[set] = serializer = new XmlSerializer(typeof(T), xOver);
            return serializer;
        }
    }

    static void IgnoreAllProperties(Type type, XmlAttributeOverrides xOver)
    {
        if (type == null || type == typeof(object) || type.IsPrimitive || type == typeof(string))
            return;
        var attrs = new XmlAttributes() { XmlIgnore = true };
        foreach (var property in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
            if (xOver[type, property.Name] == null) // Check to see if overrides for this base type were already set.
                xOver.Add(type, property.Name, attrs);
        var baseType = type.BaseType;
        if (baseType != type)
            IgnoreAllProperties(baseType, xOver);
    }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thank you for a quick response. I added the default ctor to MyClass, and made the setter public, but serialization still failed with the exception "There was an error reflecting type 'MyClass'" (the inner exception is "Xtensive.Orm.Session cannot be serialized because it does not have a parameterless constructor."). Entity is a DataObjects.NET base class to which I have no practical access, so I don't think suppressing properties is going to work, even if I could identify all the properties. It looks like I'm going to be stuck with a separate DTO class. Thanks again for your help. – Qodex Nov 17 '14 at 22:04
  • @Qadence - what was the full "ToString()" output of the exception including the inner exception? – dbc Nov 17 '14 at 22:08
  • @Qadence - from the message I infer `Session` must be a property of `Entity`. Are you still seeing this exception after using `XmlSerializationIgnoreOverrideCreator` to mark all properties of the base class of `MyClass` as `XmlIgnore`? – dbc Nov 17 '14 at 22:21
  • Here's the ToString(): System.InvalidOperationException: There was an error reflecting type 'HelloWorldDO.MyClass'. ---> System.InvalidOperationException: Cannot serialize member 'Xtensive.Orm.SessionBound.Session' of type 'Xtensive.Orm.Session', see inner exception for more details. ---> System.InvalidOperationException: Xtensive.Orm.Session cannot be serialized because it does not have a parameterless constructor. – Qodex Nov 18 '14 at 18:24
  • @Qadence - `Session` is a [property](http://www.nudoq.org/#!/Packages/Xtensive.Orm/Xtensive.Orm/SessionBound/P/Session) of a base class of [`Entity`](http://www.nudoq.org/#!/Packages/Xtensive.Orm/Xtensive.Orm/Entity). I can't reproduce the problem using `XmlSerializationIgnoreOverrideCreator`, but I don't have access to your code library. Can you show how you are constructing and using the override serializer? – dbc Nov 18 '14 at 20:06
  • The base class (Entity) is supplied by a vendor as part of the DataObjects.NET assembly. Looking at the metadata, Session is one of the properties. I will try XmlSerializationIgnoreOverrideCreator and see what happens. – Qodex Nov 18 '14 at 20:29
  • 1
    That did it! The base class is ignored. Here's the result: `\r\n\r\n 1\r\n Joe\r\n`. Thanks! – Qodex Nov 18 '14 at 20:37