views:

858

answers:

5

OK. I'm trying to work on communicating with the Pivotal Tracker API, which only returns data in an XML format. I have the following XML that I'm trying to deserialize into my domain model.


<?xml version="1.0" encoding="UTF-8"?>
<stories type="array" count="2" total="2">
  <story>
    <id type="integer">2909137</id>
    <project_id type="integer">68153</project_id>
    <story_type>bug</story_type>
    <url>http://www.pivotaltracker.com/story/show/2909137&lt;/url>
    <current_state>unscheduled</current_state>
    <description></description>
    <name>Test #2</name>
    <requested_by>Anthony Shaw</requested_by>
    <created_at type="datetime">2010/03/23 20:05:58 EDT</created_at>
    <updated_at type="datetime">2010/03/23 20:05:58 EDT</updated_at>
  </story>
  <story>
    <id type="integer">2909135</id>
    <project_id type="integer">68153</project_id>
    <story_type>feature</story_type>
    <url>http://www.pivotaltracker.com/story/show/2909135&lt;/url>
    <estimate type="integer">-1</estimate>
    <current_state>unscheduled</current_state>
    <description></description>
    <name>Test #1</name>
    <requested_by>Anthony Shaw</requested_by>
    <created_at type="datetime">2010/03/23 20:05:53 EDT</created_at>
    <updated_at type="datetime">2010/03/23 20:05:53 EDT</updated_at>
  </story>
</stories>

My 'story' object is created as follows:


public class story
{
     public int id { get; set; }
     public int estimate { get; set; }
     public int project_id { get; set; }

        public string story_type { get; set; }
        public string url { get; set; }
        public string current_state { get; set; }
        public string description { get; set; }
        public string name { get; set; }
        public string requested_by { get; set; }
        public string labels { get; set; }
        public string lighthouse_id { get; set; }
        public string lighthouse_url { get; set; }
        public string owned_by { get; set; }
        public string accepted_at { get; set; }
        public string created_at { get; set; }

        public attachment[] attachments { get; set; }
        public note[] notes { get; set; }
    }

When I execute my deserialization code, I receive the following exception:


Exception:
   There is an error in XML document (2, 2).

Inner Exception:
   <stories xmlns=''> was not expected.

I can deserialize the individual stories just fine, I just cannot deserialize this xml into an array of 'story' objects

And my deserialization code (value is a string of the xml)


var byteArray = Encoding.ASCII.GetBytes(value);
var stream = new MemoryStream(byteArray);
var deserializedObject = new XmlSerializer(typeof (story[])).Deserialize(stream)

Does anybody have any ideas?

+1  A: 

XMSerializer expects an XML Namespace with which to understand your XML from.

xmlns="http://schemas.microsoft.com"

... ought to do. See the XML sample at the bottom of this page.

BigChrisDiD
The XML I have is generated by the Pivotal Tracker API's RESTful service. I suppose if I had to I could edit it, but I'd rather not have to, since I'm using the deserialization method to do multiple classes, not just the story
Anthony Shaw
A: 

I would recommend that you generate an XSD from some sample XML you get from the web service. Then, with that XSD, you can generate the classes that have all the proper serialization attributes affixed to them.

  1. To generate a schema (unless you prefer to write your own), open the sample XML file in Visual Studio, and select the XML -> Create Schema menu option. Save that XSD.
  2. To generate the classes, run the XSD command from the VS command prompt. If you run it without parameters, it'll show you the command-line parameters you can use.
  3. Now you can create a type-safe XML serializer.
Jacob
+3  A: 

The problem is that you have no property named "stories". The XML Serializer has no idea what to do with the stories element when it sees it.

One thing you could try is to create a "stories" class:

public class stories : List<story> {}

and use

var byteArray = Encoding.ASCII.GetBytes(value);
stories deserializedObject  = null;
using (var stream = new MemoryStream(byteArray))
{
    var storiesSerializer = new XmlSerializer(typeof (stories));
    deserializedObject = (stories)storiesSerializer .Deserialize(stream);
}
John Saunders
gave it a shot, because I liked your implementation as it inheriting the List<story> the best, but it didn't work. thanks for giving it a shot tho :)
Anthony Shaw
@Anthony: didn't work in what way?
John Saunders
You can avoid having a separate class just for the array. See my example below.
luke
+2  A: 

Try something like

public class stories
{
    [XmlElement("story")]
    public story[] storyarray { get; set; }
}

...

var byteArray = Encoding.ASCII.GetBytes(value);
XmlSerializer serializer = new XmlSerializer(typeof(stories));
stories myStories = null;

using (var stream = new MemoryStream(byteArray))
{
    myStories = (stories)serializer.Deserialize(stream);
}

foreach (story stor in myStories.storyarray)
    Console.WriteLine(stor.story_type);

Edit: Updated code sample to use using statement based on feedback.

Anthony Pegram
thanks... worked like a charm
Anthony Shaw
@Anthony Shaw: be sure to put the `MemoryStream` into a `using` block the way I did. Otherwise, you'll leak resources if an exception is thrown.
John Saunders
@AnthonyPegram: thanks but you also want `myStories = (stories) serialiser.Deserialize(stream)`.
John Saunders
Good eye, John. I didn't think about that adjustment to the original code. And d'oh! That's what I get for editing code in the editor here rather than in the IDE!
Anthony Pegram
You don't have to bother to stub out the root class only containing the array. I posted an example below.
luke
+1  A: 

Let me offer a more concise solution. Set your deserialization up to look like this:

var deserializedObject = new XmlSerializer(typeof(story[]), new XmlRootAttribute("stories")).Deserialize(stream);

By specifying that second parameter in the XmlSerializer, you can avoid having to stub out that class. It lets the serializer know what the root element's name is.

For this to work, the name of the class that represents the array-element type must exactly match the XML name, e.g. class story {}, <story>. You can get around this (and I'd recommend it as a best practice anyway) by specifying the XmlType:

[XmlType("story")]
public class Story
{
...
}

I prefer to do this as it frees me from being stuck with the XML type name.

luke
This helped me out, a much neater way of doing things IMO.
DaRKoN_