Search code examples
c#.netserializationxml-serialization

Serialising an XML element and XML array item of the same name


I want to serialise XML documents from a third-party service that either come in as any of the two formats (I added indenting for easier readability):

1.

<STADMessage>Invalid Request, no content provided!</STADMessage>

2.

<STADMessage>
    <Message>Invalid Request, see log for detail using reference: ASDFL210359872305982035</Message>
</STADMessage>

Right now I'm hacking the XML document before it gets serialised with the following code

xmlDocument.Replace("<STADMessage><Message>", "<STADMessages><Message>")
           .Replace("</Message></STADMessage>", "</Message></STADMessages>");

Snippet of the serialised class

[XmlElement(ElementName = "STADMessage", IsNullable = true)]
public string STADMessage { get; set; }

[XmlArray(ElementName = "STADMessages", IsNullable = true)]
[XmlArrayItem("Message", typeof(string))]
public List<string> STADMessages { get; set; }

Is there a cleaner way?


Solution

  • If you can get them to change it to a proper structure as @FrankerZ recommends, that would be ideal. If you can't, I hope this helps.

    You can account for the variation using a custom serialization object that deserializes differently based on the incoming node type.

    Change your STADMessage property's type to the custom type (I'll call it STADMessage for the heck of it):

    [XmlElement(ElementName = "STADMessage", IsNullable = true)]
    public STADMessage STADMessage { get; set; }
    

    And here's the STADMessage class:

    public class MySTADMessage : IXmlSerializable
    {
        public string Message { get; set; }
    
        public XmlSchema GetSchema()
        {
            return null;
        }
    
        public void ReadXml(XmlReader reader)
        {
            // IsNullable = true is ignored, apparently.  You won't get an actual
            // null for properties deserialized this way because the serializer
            // already created an instance of this class.
            if (reader.GetAttribute("nil", XmlSchema.InstanceNamespace) == "true")
                return;
    
            reader.ReadStartElement();
    
            while (reader.NodeType == XmlNodeType.Whitespace)
                reader.Read();
    
            if (reader.NodeType == XmlNodeType.Text)
            {
                Message = reader.ReadContentAsString();
            }
            else if (reader.NodeType == XmlNodeType.Element)
            {
                if (reader.Name != "Message")
                    throw new Exception("Unexpected element name.");
    
                reader.ReadStartElement();
                if (reader.NodeType == XmlNodeType.Text)
                {
                    Message = reader.ReadContentAsString();
                }
                else
                {
                    throw new Exception("Unexpected node type.");
                }
                reader.ReadEndElement();
            }
            else
            {
                throw new Exception("Unexpected node type.");
            }
            reader.ReadEndElement();
        }
    
        public void WriteXml(XmlWriter writer)
        {
            // Not having the extra Message element is simpler.
            writer.WriteString(Message);
        }
    }
    

    It's crude, it doesn't quite implement IXmlSerializable correctly by today's standards, and it probably doesn't account for everything, but it should get you started.