2

I am trying to write classes for deserializing an XML response from some API.

Here is a sample response. When I query for ObjectA,

<Response>
    <Status>0</Status>
    <Message>Ok</Message>
    <Data>
        <ObjectAs>
            <Count>2</Count>
            <ObjectA>...</ObjectA>
            <ObjectA>...</ObjectA>
        </ObjectAs>
    </Data>
</Response>

When I query for ObjectB,

<Response>
    <Status>0</Status>
    <Message>Ok</Message>
    <Data>
        <ObjectBs>
            <Count>1</Count>
            <ObjectB>...</ObjectB>
        </ObjectBs>
    </Data>
</Response>

I am trying to create a generic Response class, but everything I tried seems to be futile.

I can't change the response structure. And I am trying to avoid writing a new Response class for each of the response type.

Notice how under Data, each API response is different. For ObjectA it is <ObjectAs> and for ObjectB it is <ObjectBs>.

Here is my ApiResponse class,

[XmlRoot(ElementName = "Response")]
    public class ApiResponse<T>
    {
        public int Code { get; set; }

        public string Message { get; set; }

        [XmlAnyElement("Data")]
        public XmlElement DataElement { get; set; }

        [XmlIgnore]
        public List<T> Data
        {
            get
            {
                {
                    return null; // How do I parse and return the list of Objects (A, B, C, whatever?)
                }
            }
        }
    }

Here is my sample API XML response, when I query for devices.

<Response>
  <Code>0</Code>
  <Message>OK</Message>
  <Data>
    <Devices>
      <Count>2</Count>
      <Device>
        <Name>0001</Name>
        <Active>TRUE</Active>
        <DeviceType>1</DeviceType>
        <Address>192.168.0.75</Address>
        <Port>80</Port>
        <Memo/>
      </Device>
      <Device>
        <Name>0002</Name>
        <Active>TRUE</Active>
        <DeviceType>1</DeviceType>
        <Address>192.168.0.78</Address>
        <Port>80</Port>
      </Device>
    </Devices>
  </Data>
</Response>

And when I query for users,

<Response>
  <Code>0</Code>
  <Message>OK</Message>
  <Data>
    <Users>
      <Count>1</Count>
      <User>
        <Name>Administrator</Name>
        <Group>Admins</Group>
      </User>
    </Users>
  </Data>
</Response>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DPJ
  • 21
  • 2
  • Possible duplicate of [C# Get all text from xml node including xml markup using Visual studio generated class](https://stackoverflow.com/questions/40715261/c-sharp-get-all-text-from-xml-node-including-xml-markup-using-visual-studio-gene) – Progman Sep 07 '19 at 10:20
  • @Progman This is a different problem.Notice how under Data, each api response is different. For ObjectA it is ObjectAs and for ObjectB it is ObjectBs. Also, there is a Count element not just an array of Objects. – DPJ Sep 07 '19 at 10:44
  • Have you tried using the `[XmlAnyElement]` attribute? Please [edit] your question to include your attempts to use the `[XmlAnyElement]` attribute and how it failed to get the values/result you want. – Progman Sep 07 '19 at 10:55
  • Create a base type the both object A & B both inherit. The you need to use XmlInclude property. This method add an additional type attribute to the xml. See : https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlincludeattribute?view=netframework-4.8 – jdweng Sep 07 '19 at 11:05

1 Answers1

0

Here is a generic solution using LINQ to XML that works:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            ApiResponse apiResponse = new ApiResponse(FILENAME);
        }
    }


    public class ApiResponse
    {
        public int code { get; set; }
        public string message { get; set; }
        public string _type { get; set; }
        public int count { get; set; }

        public Dictionary<string, Dictionary<string, string>> dict { get; set; }


        public ApiResponse(string filename)
        {
            XDocument doc = XDocument.Load(filename);
            XElement response = doc.Root;
            code = (int)response.Element("Code");
            message = (string)response.Element("Message");

            XElement types = (XElement)response.Element("Data").FirstNode;
            _type = types.Name.LocalName;
            count = (int)types.Element("Count");
            dict = types.Elements().Where(x => x.Name.LocalName != "Count")
                .GroupBy(x => (string)x.Element("Name"), y => y.Elements()
                    .GroupBy(a => a.Name.LocalName, b => (string)b)
                    .ToDictionary(a => a.Key, b => b.FirstOrDefault()))
                .ToDictionary(x => x.Key, y => y.FirstOrDefault());
        }
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • Thank you. But this is not what I am trying to achieve. What I am trying to do is a generic class which deserializes the xml in the question. I am not even sure if it is possible. – DPJ Sep 07 '19 at 21:58
  • Why do you need a class? You can always take the strings in the dictionary and make you classes. – jdweng Sep 08 '19 at 10:00