Search code examples
c#xmlserializationxsi

Xml List Serialization and Node Type Names


Ive come across multiple questions and answers on here but none specific to my situation.

I have a class 'Entity' with multiple classes that extend off of it. I want the serialization to hit the list and understand and use the type of each item for the node name.

Now, I can use what is commented out (define each array item in the main class and define the name of such by using [XmlArrayItem("Subclass1", typeof(subclass1)] but I want to keep all definitions in their subclass and I will be having too many subclasses to define everything in the main entity class...Is there anyway to achieve this?

I have tried using [XmlType(TypeName="...")] for the subclasses and so on but that did not work.

[Serializable]
[XmlInclude(typeof(Subclass1))]
[XmlRoot("Entity")]
public class Entity{

    [XmlArray("CausedBy")]
    //[XmlArrayItem("Subclass1", typeof(subclass1))]
    //[XmlArrayItem("Sublcass2", typeof(Subclass2))]
    public List<Entity> CausedBy { get; set; }

}

[Serializable]
[XmlRoot("Subclass1")]
[XmlInclude(typeof(Subclass2))]
public class Subclass1:Entity{
    //Code...
}

[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2:Subclass1{
  //Code...
}

Serializing the above code after creating an entity and adding a Subclass1 and Subclass2 to the list 'CausedBy' class results in the following:

<Entity>
  <CausedBy>
    <Entity ... xsi:type="SubClass1" />
    <Entity ... xsi:type="SubClass2" />
   </CausedBy>
<Entity>

I would like the output to show:

 <Entity>
      <CausedBy>
        <SubClass1 .../>
        <SubClass2 .../>
       </CausedBy>
    <Entity>

Solution

  • Since I totally failed to read the question to begin with, here's a new answer (it's a bit of a tl;dr, so you can always skip to the end and follow the link):

    It isn't possible to get the built in serializer class to work because you don't wish to add the attributes that it needs to be able to operate. Your only option is to seralize the class yourself, however, this need not be as tedious as it sounds; I had a similar issue a few years ago with DataGridView in virtual mode and produced a generic virtualizer that could be used to virtualize the data for display; it used a custom attribute:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class showColumnAttribute : System.Attribute
    {
        ///<summary>Optional display format for column</summary>
        public string Format;
        ///<summary>Optional Header string for column<para>Defaults to propety name</para></summary>
        public string Title;
        ///<summary>Optional column edit flag - defaults to false</summary>
        public bool ReadOnly;
        ///<summary>Optional column width</summary>
        public int Width;
        ///<summary>
        ///Marks public properties that are to be displayed in columns
        ///</summary>
        public showColumnAttribute()
        {
            Format = String.Empty;
            Title = String.Empty;
            ReadOnly = false;
            Width = 0;
        }
    }
    

    And a constructor:

        ///<summary>
        ///Extracts the properties of the supplied type that are to be displayed
        ///<para>The type must be a class or an InvalidOperationException will be thrown</para>
        ///</summary>
        public Virtualiser(Type t)
        {
            if (!t.IsClass)
                throw new InvalidOperationException("Supplied type is not a class");
    
            List<VirtualColumnInfo> definedColumns = new List<VirtualColumnInfo>();
            PropertyInfo[] ps = t.GetProperties();
            MethodInfo mg, ms;
    
            for (int i = 0; i < ps.Length; i++)
            {
                Object[] attr = ps[i].GetCustomAttributes(true);
    
                if (attr.Length > 0)
                {
                    foreach (var a in attr)
                    {
                        showColumnAttribute ca = a as showColumnAttribute;
                        if (ca != null)
                        {
                            mg = ps[i].GetGetMethod();
                            if (mg != null)
                            {
                                ms = ps[i].GetSetMethod();
                                definedColumns.Add
                                (
                                    new VirtualColumnInfo
                                    (
                                        ps[i].Name, ca.Width, ca.ReadOnly, ca.Title == String.Empty ? ps[i].Name : ca.Title, 
                                        ca.Format, mg, ms
                                    )
                                );
                            }
                            break;
                        }
                    }
                }
            }
            if (definedColumns.Count > 0)
                columns = definedColumns.ToArray();
        }
    

    This extracts the public properties of the class and supplies marked items to the DataGridView as columns together with a header, format, etc.

    The effect of all of this (and the rest of the missing code) was that any type could be virtualized in a dataGridView simply by tagging public properties and calling the virtualizer once for a given type:

        #region Virtualisation
        static readonly Virtualiser Virtual = new Virtualiser(typeof(UserRecord));
        [XmlIgnore] // just in case!
        public static int ColumnCount { get { return Virtual.ColumnCount; } }
        public static VirtualColumnInfo ColumnInfo(int column)
        {
            return Virtual.ColumnInfo(column);
        }
    
        public Object GetItem(int column)
        {
            return Virtual.GetItem(column, this);
        }
        /*
        ** The supplied item should be a string - it is up to this method to supply a valid value to the property
        ** setter (this is the simplest place to determine what this is and how it can be derived from a string).
        */
        public void SetItem(int column, Object item)
        {
            String v = item as String;
            int t = 0;
            if (v == null)
                return;
            switch (Virtual.GetColumnPropertyName(column))
            {
                case "DisplayNumber":
                    if (!int.TryParse(v, out t))
                        t = 0;
    
                    item = t;
                    break;
            }
            try
            {
                Virtual.SetItem(column, this, item);
            }
            catch { }
        }
        #endregion
    

    The number of columns, their properties and order can be specified automatically by creating a number of public properties derived from the class data:

            #region Display columns
        [showColumn(ReadOnly = true, Width = 100, Title = "Identification")]
        public String DisplayIdent
        {
            get
            {
                return ident;
            }
            set
            {
                ident = value;
            }
    
        }
        [showColumn(Width = 70, Title = "Number on Roll")]
        public int DisplayNumber
        {
            get
            {
                return number;
            }
            set
            {
                number = value;
            }
        }
        [showColumn(Width = -100, Title = "Name")]
        public string DisplayName
        {
            get
            {
                return name == String.Empty ? "??" : name;
            }
            set
            {
                name = value;
            }
        }
        #endregion
    

    This would virtualize any class for dataGridView to display and edit data and I used it many times over the years and the extraction of properties to display is exactly what is required for XML serialization, indeed, it has a lot of the same characteristics.

    I was going to adapt this method to do the same job for XML serialization but someone has already done it at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=474453, I hope you can make use of this method to solve your problem.