简体   繁体   中英

Serialize array of derived type property of derived type

I've got a simple .Net framework C# console app that serializes aa class that is of a derived type, where a property is also of a derived type.

The derived classes have names that are the same as the base class, but are in a different namespace to prevent them from clashing. It seems though that the reflection the XmlSerializer uses does not work with this too well. Maybe there is some way of wrangling the attributes that I can still end up with the base class using pretty names (as it will be a DLL interface when used) and the XML also using pretty names (as it will be human editable)... pretty names for the derived classes are not required (though would be a bonus).

The XML would hopefully look like:

<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Details>
    <Detail>
      <Description>bald</Description>
    </Detail>
    <Detail>
      <Description>red tie</Description>
    </Detail>
  </Details>
</Person>

But the closest I can get without exceptions is where the <Detail> elements are

    <Detail xsi:type="DerivedDetail"> ... </Detail>

Having to add this xs:type attribute is not the best for human-editable XML.

This is achieved with the below C# code. If I remove the marked XmlType attribute then the element should serialize without the xsi:type attribute, but instead I get an exception:

InvalidOperationException: Types 'Test.Detail' and 'Test.Xml.Detail' both use the XML type name, 'Detail', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.

I tried marking the derived Xml.Detail class as an anonymous XML type but then the exception reads:

InvalidOperationException: Cannot include anonymous type 'Test.Xml.Detail'.

I have read many similar questions but have not encountered anything that solves this just yet.

In this code below Person is an abstract class that has a property that is an array of the abstract type Detail . These types are derived by Xml.Person and Xml.Detail respectively. The program creates a test Xml.Person object and attempts to serialize it:

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

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create test details array
            var TestDetails = new Xml.Detail[] 
            {
                new Xml.Detail
                {
                    Description = "bald"
                },
                new Xml.Detail
                {
                    Description = "red tie"
                }
            };

            // create test person object that holds details array
            var TestBar = new Xml.Person()
            {
                Details = TestDetails
            };

            // serialize the person object
            var s = new Xml.Serializer();
            var TestOutput = s.Serialize(TestBar);

            Console.WriteLine(TestOutput);
        }
    }

    // base classes
    public abstract class Person
    {
        public abstract Detail[] Details { get; set; }
    }

    public abstract class Detail
    {
        public abstract string Description { get; set; }
    }

    namespace Xml
    {
        // derived classes
        [Serializable]
        [XmlType(AnonymousType = true)]
        [XmlRoot(IsNullable = false)]
        public class Person : Test.Person
        {
            [XmlArrayItem("Detail", typeof(Detail))]
            [XmlArray(IsNullable = false)]
            public override Test.Detail[] Details { get; set; }
        }

        // This attribute makes serialization work but also adds the xsi:type attribute
        [XmlType("DerivedDetail")]
        [Serializable]
        public class Detail : Test.Detail
        {
            public override string Description { get; set; }
        }

        // class that does serializing work
        public class Serializer
        {
            private static XmlSerializer PersonSerializer = 
                new XmlSerializer(typeof(Person), new Type[] { typeof(Detail) });

            public string Serialize(Test.Person person)
            {
                string Output = null;
                var Stream = new MemoryStream();
                var Encoding = new UTF8Encoding(false, true);

                using (var Writer = new XmlTextWriter(Stream, Encoding))
                {
                    Writer.Formatting = Formatting.Indented;
                    PersonSerializer.Serialize(Writer, person);
                    Output = Encoding.GetString(Stream.ToArray());
                }
                Stream.Dispose();
                return Output;
            }
        }
    }
}

Not sure why you're using base classes instead of interfaces when you don't have any member fields. Regardless, I assumed you wanted Xml.Person to be a concrete instantiation of abstract Person or any classes derived from abstract Person without decorating abstract Person with XML attributes. I accomplished this by forcing abstract Person to become a concrete instantiation of Xml.Person before serializing it. Please replace XmlSerializationProject with Test .

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

namespace XmlSerializationProject
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create test details array
            var TestDetails = new Xml.Detail[]
            {
                new Xml.Detail
                {
                    Description = "bald"
                },
                new Xml.Detail
                {
                    Description = "red tie"
                }
            };

            // create test person object that holds details array
            var TestBar = new Xml.Person()
            {
                Details = TestDetails
            };

            // serialize the person object
            var s = new Xml.Serializer();
            var TestOutput = s.Serialize(TestBar);

            Console.WriteLine(TestOutput);
            Console.ReadKey();
        }
    }

    // base classes
    public abstract class Person
    {
        public abstract Detail[] Details { get; set; }
    }

    public abstract class Detail
    {
        public abstract string Description { get; set; }
    }

    namespace Xml
    {
        [Serializable]
        [XmlType(AnonymousType = true)]
        [XmlRoot(IsNullable = false)]
        public class Person : XmlSerializationProject.Person
        {
            public Person()
            { }

            public Person(XmlSerializationProject.Person person)
            {
                // Deep copy
                if (person.Details == null) return;
                this.Details = new Detail[person.Details.Length];
                for (int i = 0; i < person.Details.Length; i++)
                {
                    this.Details[i] = new Detail { Description = person.Details[i].Description };
                }
            }

            [XmlArray(ElementName = "Details")]
            [XmlArrayItem("Detail", typeof(Detail))]
            [XmlArrayItem("ODetail", typeof(XmlSerializationProject.Detail))]
            public override XmlSerializationProject.Detail[] Details
            {
                get;
                set;
            }
        }

        [Serializable]
        public class Detail : XmlSerializationProject.Detail
        {
            public override string Description { get; set; }
        }

        // class that does serializing work
        public class Serializer
        {
            private static readonly XmlSerializer PersonSerializer;

            private static Serializer()
            {
                var xmlAttributeOverrides = new XmlAttributeOverrides();
                // Change original "Detail" class's element name to "AbstractDetail"
                var xmlAttributesOriginalDetail = new XmlAttributes();
                xmlAttributesOriginalDetail.XmlType = new XmlTypeAttribute() { TypeName = "AbstractDetail" };
                xmlAttributeOverrides.Add(typeof(XmlSerializationProject.Detail), xmlAttributesOriginalDetail);

                // Ignore Person.Details array
                var xmlAttributesOriginalDetailsArray = new XmlAttributes();
                xmlAttributesOriginalDetailsArray.XmlIgnore = true;
                xmlAttributeOverrides.Add(typeof(XmlSerializationProject.Person), "Details", xmlAttributesOriginalDetailsArray);

                PersonSerializer = new XmlSerializer(
                    typeof(Person), xmlAttributeOverrides, new Type[] { typeof(Detail) }, new XmlRootAttribute(), "default");
            }

            public string Serialize(XmlSerializationProject.Person person)
            {
                return Serialize(new Person(person));
            }

            public string Serialize(Person person)
            {
                string Output = null;
                var Stream = new MemoryStream();
                var Encoding = new UTF8Encoding(false, true);

                using (var Writer = new XmlTextWriter(Stream, Encoding))
                {
                    Writer.Formatting = Formatting.Indented;
                    PersonSerializer.Serialize(Writer, person);
                    Output = Encoding.GetString(Stream.ToArray());
                }
                Stream.Dispose();
                return Output;
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM