Search code examples
c#xmlxmlserializer

XmlSerializer & Paste XML as classes


I have a web service that is returning some xml. There is no corresponding xsd.

I used the Paste Special -> Paste XML as classes functionality in visual studio to generate the classes to use with XmlSerializer:

When the code executes the line

--> XmlSerializer xmlSerialize = new XmlSerializer(typeof(table)); <--
    table t = (table)xmlSerialize.Deserialize(new StringReader(soapResult));

it throws an exception:

System.InvalidOperationException: 'Unable to generate a temporary class (result=1).
error CS0030: Cannot convert type 'string[]' to 'string'
error CS0029: Cannot implicitly convert type 'string' to 'string[]'

The generated classes look like so:

// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
    /// <remarks/>
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
    [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
    public partial class table
    {

        private string[] labelField;

        private string[] classnameField;

        private string[] datatypeField;

        private string[][] rowField;


        /// <remarks/>
        [System.Xml.Serialization.XmlArrayItemAttribute("d", IsNullable = false)]
        public string[] label
        {
            get
            {
                return this.labelField;
            }
            set
            {
                this.labelField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlArrayItemAttribute("d", IsNullable = false)]
        public string[] classname
        {
            get
            {
                return this.classnameField;
            }
            set
            {
                this.classnameField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlArrayItemAttribute("d", IsNullable = false)]
        public string[] datatype
        {
            get
            {
                return this.datatypeField;
            }
            set
            {
                this.datatypeField = value;
            }
        }

        /// <remarks/>
        [System.Xml.Serialization.XmlArrayItemAttribute("d", typeof(string), IsNullable = true)]
        public string[][] row
        {
            get
            {
                return this.rowField;
            }
            set
            {
                this.rowField = value;
            }
        }
    }

The problem is with the row field, as the others work ok. If I change

[System.Xml.Serialization.XmlArrayItemAttribute("d", typeof(string), IsNullable = true)]

to

[System.Xml.Serialization.XmlArrayItemAttribute("d", typeof(string[]), IsNullable = true)]

the error goes away, but my object just has an array of empty string arrays.

The XML data being deserialized (condensed for simplicity) looks like this

<?xml version="1.0" encoding="UTF-8"?>
<table>
   <label>
      <d>JobDefinition.SearchName</d>
      <d>Job.JobDefinition</d>
   </label>
   <classname>
      <d>JobDefinition.SearchName</d>
      <d>Job.JobDefinition</d>
   </classname>
   <datatype>
      <d>String</d>
      <d>obj.JobDefinition</d>
   </datatype>
   <row>
      <d>AB_DEFG_QA_RUN</d>
      <d>A_JOB_Run</d>
   </row>
   <row>
      <d>AB_DEFG_QA_RUN</d>
      <d>B_JOB_Run</d>
   </row>
</table>

Solution

  • Your problem looks identical to the one described in the Final Update of this answer to How to serialize List<List<object>>?, namely that xsd.exe (and thus Paste XML as Classes) is having trouble inferring a correct data model for XML containing a repeating element that contains nested repeating elements, here <row> and <d>:

    <table>
       <row>
          <d>Value of repeating element inside a repeating element.</d>
          <d>Value of repeating element inside a repeating element.</d>
       </row>
       <row>
          <d>Value of repeating element inside a repeating element.</d>
          <d>Value of repeating element inside a repeating element.</d>
       </row>
    </table>
    

    The recommendation in that answer is to use a different code generation tool or construct the classes manually. I uploaded your XML to https://xmltocsharp.azurewebsites.net/ and, after merging duplicate classes, obtained the following data model:

    [XmlRoot(ElementName = "row")]
    public class Row
    {
        [XmlElement(ElementName = "d")]
        public List<string> D { get; set; }
    }
    
    [XmlRoot(ElementName = "table")]
    public class Table
    {
        [XmlElement(ElementName = "label")]
        public Row Label { get; set; }
        [XmlElement(ElementName = "classname")]
        public Row Classname { get; set; }
        [XmlElement(ElementName = "datatype")]
        public Row Datatype { get; set; }
    
        [XmlElement(ElementName = "row")]
        public List<Row> Row { get; set; }
    }
    

    Using this I am able to deserialize and re-serialize your XML successfully without data loss. Notes:

    • The code-generation tool generated distinct yet identical classes for Label, Classname, Datatype and Row. I decided to merge them all into a single Row type, and use it for all the relevant members of Table.

    Sample fiddle here.