mike
mike

Reputation: 1683

In C# I can't get my XML to deserialize into an object

I've looked through the various questions on here similar to this, but can't get my solution to work.

I'm using Visual Studio 2015 Community, building a WPF project.

I get xml from my backend API, and I'm trying to convert it into a C# object, but I can't get it to work.

This is the xml

<response>
    <computer_setting id="1" hospital_name="foo" computer_type="bar" environment="staging" label_printer_name="labels" document_printer_name="docs"/>
</response>

This is class

using System.Xml.Serialization;

namespace Casechek.Kiosk
{
    [XmlRoot("response")]
    public class ComputerSettingResponse
    {
        [XmlElement("computer_setting")]
        internal ComputerSetting Settings { get; set; }
    }

    internal class ComputerSetting
    {
        [XmlAttribute("id")]
        internal string Id { get; set; }
        [XmlAttribute("hospital_name")]
        internal string HospitalName { get; set; }
        [XmlAttribute("computer_type")]
        internal string ComputerType { get; set; }
        [XmlAttribute("environment")]
        internal string Environment { get; set; }
        [XmlAttribute("label_printer_name")]
        internal string LabelPrinterName { get; set; }
        [XmlAttribute("document_printer_name")]
        internal string DocumentPrinterName { get; set; }
    }
}

And this is my attempt to deserialize it

// Get ComputerSettings
String _Url = this.ApiUrl
   + "/api1/hospitals/foo/settings.xml"
   + "?access_token=" + Authentication.AccessToken;

XmlSerializer _Serializer = new XmlSerializer(typeof(ComputerSettingResponse));
ComputerSettingResponse _ComputerSettingResponse = new ComputerSettingResponse();
using (XmlTextReader _XmlReader = new XmlTextReader(_Url))
{
    _ComputerSettingResponse = (ComputerSettingResponse)_Serializer.Deserialize(_XmlReader);
    Debug.WriteLine(_ComputerSettingResponse.Settings.Environment);
}

But this throws NullReference exception when it gets to Debug.WriteLine()

{"Object reference not set to an instance of an object."}

I've checked that the url is returning the xml properly, so it must me a poorly constructed class, or I'm not doing the deserialization properly.

Upvotes: 2

Views: 1474

Answers (4)

granadaCoder
granadaCoder

Reputation: 27904

Once XDocument came out ( matched with Linq ) , I stopped all the Xml Attribute voodoo. This "mapping" code is simple and straight forward.

public class ComputerSettingResponse
{
    internal ComputerSetting Settings { get; set; }
}

internal class ComputerSetting
{

    internal string Id { get; set; }

    internal string HospitalName { get; set; }

    internal string ComputerType { get; set; }

    internal string Environment { get; set; }

    internal string LabelPrinterName { get; set; }

    internal string DocumentPrinterName { get; set; }
}

            string xmlString = @"<response>
    <computer_setting id=""1"" hospital_name=""foo"" computer_type=""bar"" environment=""staging"" label_printer_name=""labels"" document_printer_name=""docs""/>
</response>          ";

        XDocument xDoc = XDocument.Parse(xmlString);

        //XNamespace ns = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
        string ns = string.Empty;


        List<ComputerSettingResponse> collection = new List<ComputerSettingResponse>
        (
            from list in xDoc.Descendants(ns + "response")
            from item1 in list.Elements(ns + "computer_setting")
            where item1 != null
            select new ComputerSettingResponse
            {
                //note that the cast is simpler to write than the null check in your code
                //http://msdn.microsoft.com/en-us/library/bb387049.aspx
                Settings = new ComputerSetting
                (
                ) 
                {
                    Id = (string)item1.Attribute("id") ?? string.Empty,
                    HospitalName = (string)item1.Attribute("hospital_name") ?? string.Empty,
                    ComputerType = (string)item1.Attribute("computer_type") ?? string.Empty,
                    Environment = (string)item1.Attribute("environment") ?? string.Empty,
                    LabelPrinterName = (string)item1.Attribute("label_printer_name") ?? string.Empty,
                    DocumentPrinterName = (string)item1.Attribute("document_printer_name") ?? string.Empty
                }
            }
        );

        /* if you know there is only one */
        ComputerSettingResponse returnItem = collection.FirstOrDefault();

Upvotes: 1

snow_FFFFFF
snow_FFFFFF

Reputation: 3311

Visual Studio has some nice tooling to generate serializable classes from XML or JSON - just copy a sample of your XML to the clipboard, open an empty class file and paste it as classes with the following menu path in Visual Studio: Edit -> Paste Special -> Paste XML as Classes ...or, "Paste JSON as Classes" in the case of JSON.

Upvotes: 2

Charles Mager
Charles Mager

Reputation: 26223

Your properties are required to be public in order to be serialised. Per the documentation:

XML serialization is the process of converting an object's public properties and fields to a serial format (in this case, XML) for storage or transport.

Change Settings from internal to public and it will deserialise correctly (you will also have to change the class modified on ComputerSetting to public in order to compile).

As an aside, you shouldn't be using XmlTextReader, its use has been discouraged since .NET 2 was released. Per the remarks in the documentation, you should use XmlReader.Create.

Upvotes: 1

Volkan Paksoy
Volkan Paksoy

Reputation: 6977

In the above code you seem to treat "_Url" as XML but it's just the URL of the backend API, isn't it?

I tried replacing it with your sample XML and made it work only by changing the class visibility to public

So this worked for me:

        String xml = File.ReadAllText("XMLFile1.xml");
        XmlSerializer _Serializer = new XmlSerializer(typeof(ComputerSettingResponse));
        ComputerSettingResponse _ComputerSettingResponse = new ComputerSettingResponse();
        using (StringReader reader = new StringReader(xml))
        {
            _ComputerSettingResponse = (ComputerSettingResponse)_Serializer.Deserialize(reader);
            Debug.WriteLine(_ComputerSettingResponse.Settings.Environment);
        }

using the class like this:

using System.Xml.Serialization;

namespace Casechek.Kiosk
{
    [XmlRoot("response")]
    public class ComputerSettingResponse
    {
        [XmlElement("computer_setting")]
        public ComputerSetting Settings { get; set; }
    }

    public class ComputerSetting
    {
        [XmlAttribute("id")]
        public string Id { get; set; }
        [XmlAttribute("hospital_name")]
        public string HospitalName { get; set; }
        [XmlAttribute("computer_type")]
        public string ComputerType { get; set; }
        [XmlAttribute("environment")]
        public string Environment { get; set; }
        [XmlAttribute("label_printer_name")]
        public string LabelPrinterName { get; set; }
        [XmlAttribute("document_printer_name")]
        public string DocumentPrinterName { get; set; }
    }
}

I looked into why it didn't work with internal declaration and found this SO answer which helped me understand a few things about the internals of XmlSerializer: https://stackoverflow.com/a/6156822/3093396

Hope this helps.

Upvotes: 1

Related Questions