Tarta
Tarta

Reputation: 2063

Deserializing XML from Soap call into Array of Objects

I am trying to make a soap call and deserialize the answer I receive back. The soap call is made correctly but I don't manage so far to deserialize the answer into an object. Specifically speaking, the XML contained in the answer is an array of repeated data. As you can guess I want to create an array of objects out of it. I have checked other similar questions but so far none worked. Let's start by defining the whole XML.

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">ThisIsATry/RetrieveResponse</a:Action>
  </s:Header>
  <s:Body>
    <RetrieveResponse xmlns="ThisIsATry">
      <RetrieveInsurersResult xmlns:b="http://schemas.datacontract.org/2004/07/ThisIsATry" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <b:Errors xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
        <b:Message>Selected 2 records</b:Message>
        <b:Results xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
          <c:ArrayOfKeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>PersonId</c:Key>
              <c:Value>1</c:Value>
            </c:KeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>Name</c:Key>
              <c:Value>Mike</c:Value>
            </c:KeyValueOfstringstring>
          </c:ArrayOfKeyValueOfstringstring>
          <c:ArrayOfKeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>PersonId</c:Key>
              <c:Value>2</c:Value>
            </c:KeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>Name</c:Key>
              <c:Value>Henry</c:Value>
            </c:KeyValueOfstringstring>
          </c:ArrayOfKeyValueOfstringstring>
        </b:Results>
        <b:Warnings xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
      </RetrieveInsurersResult>
    </RetrieveResponse>
  </s:Body>
</s:Envelope>

The part I need to use is:

    <b:Results xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
      <c:ArrayOfKeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>PersonId</c:Key>
          <c:Value>1</c:Value>
        </c:KeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>Name</c:Key>
          <c:Value>Mike</c:Value>
        </c:KeyValueOfstringstring>
      </c:ArrayOfKeyValueOfstringstring>
      <c:ArrayOfKeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>PersonId</c:Key>
          <c:Value>2</c:Value>
        </c:KeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>Name</c:Key>
          <c:Value>Henry</c:Value>
        </c:KeyValueOfstringstring>
      </c:ArrayOfKeyValueOfstringstring>
    </b:Results>

As you can see there are 2 "objects" of type c:ArrayOfKeyValueOfstringstring>. Each object contains 2 properties of type c:KeyValueOfstringstring . Finally, each one of these properties contains a key and a value . What I need is an array of c:ArrayOfKeyValueOfstringstring, containing an array of c:KeyValueOfstringstring and the relative informations. I tried to represent this data in my c# code with the following classes:

public class ArrayOfKeyValueOfstringstring
{
    [XmlElement("ArrayOfKeyValueOfstringstring")]
    public KeyValueOfstringstring[] Value { get; set; }

}

public class KeyValueOfstringstring
{
    [XmlElement("KeyValueOfstringstring")]
    public KeyValue Pair { get; set; }

}

public class KeyValue
{
    [XmlElement("Key")]
    public string Key { get; set; }
    [XmlElement("Value")] 
    public string Value { get; set; }
}

The way I deal with the response so far is:

var result = client.UploadString(dataBaseConnectionString, soapTemplate); //calls the serive and brings back the XML
var document = XDocument.Parse(result); //gives back the first XML I posted in this question
var results = document.Descendants().FirstOrDefault(x => x.Name.LocalName == "Results"); //gives back the 2nd XML I posted

xmlNamespaceManager.AddNamespace("c", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
var oXmlSerializer = new XmlSerializer(typeof(SoapResponse[]));

//this part is wrong..
using (var mem = new MemoryStream(Encoding.UTF8.GetBytes(results.ToString())))
{
      var responseObj = (ArrayOfKeyValueOfstringstring)oXmlSerializer.Deserialize(mem);
}

Thanks in advance for the help!

Upvotes: 1

Views: 5463

Answers (2)

jdweng
jdweng

Reputation: 34421

Try xml linq

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)
        {
            XDocument doc = XDocument.Load(FILENAME);

            XElement results = doc.Descendants().Where(x => x.Name.LocalName == "Results").FirstOrDefault();
            XNamespace nsC = results.GetNamespaceOfPrefix("c");

            Dictionary<string, List<string>> dict = results.Descendants(nsC + "KeyValueOfstringstring")
               .GroupBy(x => (string)x.Element(nsC + "Key"), y => (string)y.Element(nsC + "Value"))
               .ToDictionary(x => x.Key, y => y.ToList());
        }
    }

}

Upvotes: 0

dbc
dbc

Reputation: 116533

As it so happens, ArrayOfKeyValueOfstringstring is the element name that DataContractSerializer chooses when serializing a Dictionary<string, string>, so you're going to have a much easier time using that serializer to deserialize your XML.

First, introduce the following extension method:

public static partial class DataContractSerializerExtensions
{
    public static T ToContractObject<T>(this XContainer doc, DataContractSerializer serializer = null)
    {
        if (doc == null)
            throw new ArgumentNullException();
        using (var reader = doc.CreateReader())
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(reader);
        }
    }
}

And now you can parse your XML into a List<Dictionary<string, string>> as follows:

var dictionaries = document.Descendants()
    .Where(d => d.Name.LocalName == "ArrayOfKeyValueOfstringstring")
    .Select(d => d.ToContractObject<Dictionary<string, string>>())
    .ToList();

Then later you can map the list of dictionaries to your preferred model.

However, if for whatever reason you must use XmlSerializer, instead introduce the following extension method and data model:

public static partial class XmlSerializerExtensions
{
    public static T ToObject<T>(this XContainer doc, XmlSerializer serializer = null)
    {
        if (doc == null)
            throw new ArgumentNullException();
        using (var reader = doc.CreateReader())
        {
            return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
        }
    }
}

[XmlRoot(ElementName = "KeyValueOfstringstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
public class KeyValueOfstringstring
{
    [XmlElement(ElementName = "Key", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
    public string Key { get; set; }
    [XmlElement(ElementName = "Value", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
    public string Value { get; set; }
}

[XmlRoot(ElementName = "ArrayOfKeyValueOfstringstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
public class ArrayOfKeyValueOfstringstring
{
    [XmlElement(ElementName = "KeyValueOfstringstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
    public List<KeyValueOfstringstring> KeyValueOfstringstring { get; set; }
}

And deserialize as follows:

var results = document.Descendants()
    .Where(d => d.Name.LocalName == "ArrayOfKeyValueOfstringstring")
    .Select(d => d.ToObject<ArrayOfKeyValueOfstringstring>())
    .ToList();

Notes:

  • I use XNode.CreateReader() to return an XmlReader from which an XNode can be deserialized directly. This avoids the requirement to convert selected node(s) back to a string representation and then re-parse.

  • The namespace for the <b:Results> node is "http://schemas.datacontract.org/2004/07/ThisIsATry". This namespace feels... provisional.. so I avoided using it in my answer.

All this being said, since the XML looks like it might have been generated by a wcf service, is there any chance the service provides WSDL Metadata? If so you may be able to generate a client automatically. See

For documentation on how this can be done.

Upvotes: 1

Related Questions