Search code examples
c#arraysxmlxml-deserializationixmlserializable

Object resulting from xml deserialization only has 1 element, despite xml having multiple elements


I'm trying to test the serialization for webrequests. I'm doing a unit test where I: - create a mock response from the server - deserialize that response - Compare initial object with deserialized one

The issue is one of my arrays in only partially populated where instead of all the elements, it only has one, the last one. Deserialization has to be made by hand because of xml schema limitations. Item is a partial class to separate the DTO from the xml operations

I have tried to change the attributes of the array property to

[XmlArray("items")]
[XmlArrayItemAttribute("item")]

I have tested just the serialization-deserialization of an individual item and it works.

I have checked both the xml resulting from the original serialization and the xml which is deseriliazed and they are equal.

First the item itself:

[XmlRoot("item")]
public partial class InvoiceItem
{      
    [XmlElement(ElementName = "name")]
    public string Name { get; set; }    
}

Now the array:

    [XmlArray("items")]
    [XmlArrayItemAttribute("item")]
    public InvoiceItem[] InvoiceItems
    {
        get
        {
            return this.invoiceItems;
        }
        set
        {
            this.invoiceItems = value;
        }

    }

Finally the deserializator:

    public void ReadXml(XmlReader reader)
    {
        Regex regexTaxName = new Regex(@"tax\d_name");
        Regex regexTaxType = new Regex(@"tax\d_type");
        Regex regexTaxPercent = new Regex(@"tax\d_percent");
        Regex regexTaxNumber = new Regex(@"\d");
        List<Tax> taxesList = new List<Tax>();
        while (reader.Read())
        {
            Debug.WriteLine(reader.GetAttribute("name"));
            if (reader.NodeType == XmlNodeType.Element)
            {

                if (reader.Name.Equals("name", StringComparison.Ordinal))
                {
                    reader.Read();
                    this.Name = reader.Value;
                }
                else if (reader.Name.Equals("type", StringComparison.Ordinal))
                {
                    ProductType value = ProductType.Product;
                    reader.Read();
                    if (Enums.TryParse<ProductType>(reader.Value, out value))
                    {

                        this.Type = value;
                    }
                }
                else if (reader.Name.Equals("description", StringComparison.Ordinal))
                {
                    reader.Read();
                    this.Description = reader.Value;
                }
                else if (reader.Name.Equals("unit_cost", StringComparison.Ordinal))
                {
                    float value = 0;
                    reader.Read();
                    if (float.TryParse(reader.Value, out value))
                    {

                        this.UnitCost = value;
                    }
                }
                else if (reader.Name.Equals("quantity", StringComparison.Ordinal))
                {
                    int value = 0;
                    reader.Read();
                    if (int.TryParse(reader.Value, out value))
                    {

                        this.Quantity = value;
                    }
                }
                else if (reader.Name.Equals("discount", StringComparison.Ordinal))
                {
                    float value = 0;
                    reader.Read();
                    if (float.TryParse(reader.Value, out value))
                    {

                        this.Discount = value;
                    }
                }
                else if (reader.Name.Equals("discount_type", StringComparison.Ordinal))
                {
                    NumericalSignificance value = NumericalSignificance.Percent;
                    reader.Read();
                    if (Enums.TryParse<NumericalSignificance>(reader.Value, out value))
                    {

                        this.DiscountType = value;
                    }
                }
                else if (regexTaxName.IsMatch(reader.Name))
                {
                    int taxNumber = int.Parse(regexTaxNumber.Match(reader.Name).Value, CultureInfo.CurrentCulture);

                    if (taxesList.Count < taxNumber)
                    {
                        reader.Read();
                        Tax newTax = new Tax();
                        newTax.Name = reader.Value;
                        taxesList.Add(newTax);
                    }
                    else
                    {
                        reader.Read();
                        taxesList[taxNumber-1].Name = reader.Value;
                    }
                }
                else if (regexTaxPercent.IsMatch(reader.Name))
                {
                    int taxNumber = int.Parse(regexTaxNumber.Match(reader.Name).Value, CultureInfo.CurrentCulture);
                    if (taxesList.Count > taxNumber)
                    {
                        Tax newTax = new Tax();
                        float value = 0;
                        reader.Read();
                        if (float.TryParse(reader.Value, out value))
                        {

                            newTax.TaxPercent = value;
                        }

                        taxesList.Add(newTax);
                    }
                    else
                    {
                        float value = 0;
                        reader.Read();
                        if (float.TryParse(reader.Value, out value))
                        {

                            taxesList[taxNumber-1].TaxPercent = value;
                        }
                    }
                }
                else if (regexTaxType.IsMatch(reader.Name))
                {
                    int taxNumber = int.Parse(regexTaxNumber.Match(reader.Name).Value, CultureInfo.CurrentCulture);
                    if (taxesList.Count > taxNumber)
                    {
                        Tax newTax = new Tax();
                        NumericalSignificance value = NumericalSignificance.Percent;
                        reader.Read();
                        if (Enums.TryParse<NumericalSignificance>(reader.Value, out value))
                        {

                            newTax.Type = value;
                        }

                        taxesList.Add(newTax);
                    }
                    else
                    {
                        NumericalSignificance value = NumericalSignificance.Percent;
                        reader.Read();
                        if (Enums.TryParse<NumericalSignificance>(reader.Value, out value))
                        {

                            taxesList[taxNumber-1].Type = value;
                        }
                    }
                }
            }
        }
        taxesArr = taxesList.ToArray();
    }

The problems is on the items array where the end object only has the final object instead of all the original ones.

EDIT:

An example that shows the issue:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;


[XmlRoot("invoice")]
public class Invoice
{
    [XmlArray("items")]
    private InvoiceExampleItem[] items;

    [XmlArray("items")]
    public InvoiceExampleItem[] Items
    {
        get { return this.items; }
        set { this.items = value; }
    }
}

[XmlRoot("item", Namespace = "")]
public partial class InvoiceExampleItem : IXmlSerializable
{
    [XmlElement(ElementName = "name")]
    public string Name { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element)
            {

                if (reader.Name.Equals("name", StringComparison.Ordinal))
                {
                    reader.Read();
                    this.Name = reader.Value;
                }
            }
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteElementString("name", this.Name);
    }
}




    [TestClass]
    public class ExampleTest : AutoMoqTest
    {
        [TestMethod]
        public void ExampleDeserialization()
        {
            InvoiceExampleItem item1 = new InvoiceExampleItem()
            {
                Name = "item1"
            };

            InvoiceExampleItem item2 = new InvoiceExampleItem()
            {
                Name = "item2"
            };

            InvoiceExampleItem item3 = new InvoiceExampleItem()
            {
                Name = "item3"
            };


            Invoice mockInvoice = new Invoice()
            {
                Items = new InvoiceExampleItem[] { item1, item2, item3 }
            };
            XmlDocument mockInvoiceSerialized = SerializeInvoice(mockInvoice);

            XmlDocument mockResponseXml = GenerateXmlResponse(mockInvoiceSerialized);

            GetInvoiceResponse response = DeserializeXML<GetInvoiceResponse>(mockResponseXml.OuterXml);

            Invoice resultInvoice = response.Invoice;

            if (mockInvoice.Items.Length != resultInvoice.Items.Length)
            {
                throw new Exception("wrong number of items");
            }

        }

        public XmlDocument SerializeInvoice(Invoice invoiceToSerialize)
        {
            XmlDocument toReturn = new XmlDocument();
            XmlSerializer serializer = new XmlSerializer(typeof(Invoice));
            XmlReaderSettings settings = new XmlReaderSettings();

            XmlDocument itemsDocument = GetTemplateXML();
            InvoiceExampleItem[] items = invoiceToSerialize.Items;
            MemoryStream memStm = null, tempStream = null;
            try
            {
                memStm = tempStream = new MemoryStream();

                using (StreamWriter sw = new StreamWriter(memStm, Encoding.UTF8))
                {
                    // Serialize object into raw xml
                    memStm = null;
                    serializer.Serialize(sw, invoiceToSerialize);
                    sw.Flush();

                    // parse raw xml into Xml document
                    tempStream.Position = 0;
                    settings.IgnoreWhitespace = true;
                    var xtr = XmlReader.Create(tempStream, settings);

                    toReturn.Load(xtr);
                }
            }
            finally
            {
                if (memStm != null)
                {
                    memStm.Dispose();
                }
            }

            return toReturn;
        }

        public static T DeserializeXML<T>(string responseString)
            where T : class
        {
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
            using (StringReader sr = new StringReader(responseString))
            {
                return (T)xmlSerializer.Deserialize(sr);
            }
        }

        public XmlDocument GetTemplateXML()
        {
            XmlDocument toReturn = new XmlDocument();
            var decl = toReturn.CreateXmlDeclaration("1.0", "utf-8", string.Empty);
            toReturn.AppendChild(decl);

            return toReturn;
        }

        private XmlDocument GenerateXmlResponse(XmlDocument innerXMLDocument)
        {
            XmlDocument toReturn = GetTemplateXML();

            XmlElement requestElement = toReturn.CreateElement("response");
            requestElement.SetAttribute("status", "success");
            requestElement.InnerXml = innerXMLDocument.DocumentElement.OuterXml;
            toReturn.AppendChild(requestElement);

            return toReturn;
        }

        /// <summary>
        /// Web response from creating a invoice
        /// </summary>
        [System.Xml.Serialization.XmlTypeAttribute(Namespace = "")]
        [System.Xml.Serialization.XmlRootAttribute(Namespace = "", ElementName = "response")]
        public class GetInvoiceResponse
        {
            /// <summary>
            /// Gets or sets the response status
            /// </summary>
            /// <value>
            /// reponse Status
            /// </value>
            [XmlAttribute("status")]
            public string ResponseStatus { get; set; }

            /// <summary>
            /// Gets or sets the new invoice id
            /// </summary>
            /// <value>
            /// generated by invoicera for this response
            /// </value>
            [XmlElement(ElementName = "invoice")]
            public Invoice Invoice { get; set; }
        }
    }

Solution

  • The solution was to create a class for the array and implement the IXMLSeriazable interface in that class and removing the interface from the item class.

    Then, when the xml in the items class, I cycle the tags and create an item individually, adding it to the array next.

    For some reason, the method was not exiting when it processed each <\item> tag, so I added a condition to exit the cycle.

    Here is the complete code:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    
    [XmlRoot("invoice")]
    public class Invoice
    {
        public Items items { get; set; }
    
    }
    
    [XmlRoot("item", Namespace = "")]
    public partial class InvoiceExampleItem
    {
        [XmlElement(ElementName = "name")]
        public string Name { get; set; }
    
        public XmlSchema GetSchema()
        {
            return null;
        }
    
        public void ReadXml(XmlReader reader)
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
    
                    if (reader.Name.Equals("name", StringComparison.Ordinal))
                    {
                        reader.Read();
                        this.Name = reader.Value;
                        return;
                    }
                }
            }
        }
    
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteElementString("name", this.Name);
        }
    }
    
    
    public class Items : IXmlSerializable
    {
        [XmlElement("item")]
        public List<InvoiceExampleItem> list = new List<InvoiceExampleItem>();
    
        public XmlSchema GetSchema()
        {
            return null;
        }
    
        public void ReadXml(XmlReader reader)
        {
            while (reader.ReadToFollowing("item"))
            {
                InvoiceExampleItem currentItem = new InvoiceExampleItem();
                currentItem.Name = reader.Value;
                list.Add(currentItem);
            }
        }
    
        public void WriteXml(XmlWriter writer)
        {
            foreach(InvoiceExampleItem item in list)
            {
                writer.WriteStartElement("item");
                item.WriteXml(writer);
                writer.WriteEndElement();
    
            }
        }
    }
    
    
        [TestClass]
        public class ExampleTest : AutoMoqTest
        {
            [TestMethod]
            public void ExampleDeserialization()
            {
                /**/
                InvoiceExampleItem item1 = new InvoiceExampleItem()
                {
                    Name = "item1"
                };
    
                InvoiceExampleItem item2 = new InvoiceExampleItem()
                {
                    Name = "item2"
                };
    
                InvoiceExampleItem item3 = new InvoiceExampleItem()
                {
                    Name = "item3"
                };
    
            Items items = new Items();
            InvoiceExampleItem item21 = new InvoiceExampleItem()
            {
                Name = "item1"
            };
    
            InvoiceExampleItem item22 = new InvoiceExampleItem()
            {
                Name = "item2"
            };
    
            InvoiceExampleItem item23 = new InvoiceExampleItem()
            {
                Name = "item3"
            };
            items.list.Add(item21);
            items.list.Add(item22);
            items.list.Add(item23);
    
            Invoice mockInvoice = new Invoice()
                {
                    items = items
            };
                /**/
                XmlDocument mockInvoiceSerialized = SerializeInvoice(mockInvoice);
    
                XmlDocument mockResponseXml = GenerateXmlResponse(mockInvoiceSerialized);
    
            GetInvoiceResponse test = new GetInvoiceResponse();
                GetInvoiceResponse response = DeserializeXML<GetInvoiceResponse>(mockResponseXml.OuterXml);
    
                Invoice resultInvoice = response.Invoice;
            mockResponseXml.Save("C:\\Users\\360Imprimir\\Desktop\\mockResponseXml");
            mockInvoiceSerialized.Save("C:\\Users\\360Imprimir\\Desktop\\mockInvoiceSerialized.Xml");
            if (mockInvoice.items.list.Count != resultInvoice.items.list.Count)
                {
                    throw new Exception("wrong number of items");
                }
    
            }
    
            public XmlDocument SerializeInvoice(Invoice invoiceToSerialize)
            {
                XmlDocument toReturn = new XmlDocument();
                XmlSerializer serializer = new XmlSerializer(typeof(Invoice));
                XmlReaderSettings settings = new XmlReaderSettings();
    
                XmlDocument itemsDocument = GetTemplateXML();
    
                MemoryStream memStm = null, tempStream = null;
                try
                {
                    memStm = tempStream = new MemoryStream();
    
                    using (StreamWriter sw = new StreamWriter(memStm, Encoding.UTF8))
                    {
                        // Serialize object into raw xml
                        memStm = null;
                        serializer.Serialize(sw, invoiceToSerialize);
                        sw.Flush();
    
                        // parse raw xml into Xml document
                        tempStream.Position = 0;
                        settings.IgnoreWhitespace = true;
                        var xtr = XmlReader.Create(tempStream, settings);
    
                        toReturn.Load(xtr);
                    }
                }
                finally
                {
                    if (memStm != null)
                    {
                        memStm.Dispose();
                    }
                }
    
                return toReturn;
            }
    
            public static T DeserializeXML<T>(string responseString)
                where T : class
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
                using (StringReader sr = new StringReader(responseString))
                {
                    return (T)xmlSerializer.Deserialize(sr);
                }
            }
    
            public XmlDocument GetTemplateXML()
            {
                XmlDocument toReturn = new XmlDocument();
                var decl = toReturn.CreateXmlDeclaration("1.0", "utf-8", string.Empty);
                toReturn.AppendChild(decl);
    
                return toReturn;
            }
    
            private XmlDocument GenerateXmlResponse(XmlDocument innerXMLDocument)
            {
                XmlDocument toReturn = GetTemplateXML();
    
                XmlElement requestElement = toReturn.CreateElement("response");
                requestElement.SetAttribute("status", "success");
                requestElement.InnerXml = innerXMLDocument.DocumentElement.OuterXml;
                toReturn.AppendChild(requestElement);
    
                return toReturn;
            }
    
            /// <summary>
            /// Web response from creating a invoice
            /// </summary>
            [System.Xml.Serialization.XmlTypeAttribute(Namespace = "")]
            [System.Xml.Serialization.XmlRootAttribute(Namespace = "", ElementName = "response")]
            public class GetInvoiceResponse
            {
                /// <summary>
                /// Gets or sets the response status
                /// </summary>
                /// <value>
                /// reponse Status
                /// </value>
                [XmlAttribute("status")]
                public string ResponseStatus { get; set; }
    
                /// <summary>
                /// Gets or sets the new invoice id
                /// </summary>
                /// <value>
                /// generated by invoicera for this response
                /// </value>
                [XmlElement(ElementName = "invoice")]
                public Invoice Invoice { get; set; }
            }
        }