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</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</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?
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.
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);
}
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.
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.
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.
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.
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.
Now you can create a type-safe XML serializer.
Related
I need to parse a xml document into object models that I've created but I can't figure out how to do so, I think it's because of my lack of understanding of the xml structure.
I've tried to get all the elements from the document and to create individual object from each based on their attributes I think they're called.
Here is my C# code :
var manifest = XDocument.Load(theDocument);
var allTheElements = manifest.Descendants();
foreach (var element in allTheElements)
{
//No idea how to parse each object into individual ManifestModel's
}
public class ManifestModel
{
public string Version { get; set; }
public string Resource { get; set; }
public string Size { get; set; }
public string Checksum { get; set; }
}
And here is the XML data :
<?xml version="1.0" encoding="UTF-8"?>
<manifest version="1.0.0" totalbytes="6131797">
<source uri="codeapi.io/Game/patches/">
<file resource="FooGame.sln" size="1125" checksum="530B9F1C2412A6D74EF017919074FD8966E5280D" />
<file resource=".vs\FooGame\v16\.suo" size="69120" checksum="438976A3681FDD503DB4FBFCBB5D420E9E8838DD" />
</source>
</manifest>
Just like we have json2csharp for JSON, we have Xml2Csharp for XML. There are probably lots of other sites that will do this.
Paste your XML and it generates this:
[XmlRoot(ElementName="file")]
public class File {
[XmlAttribute(AttributeName="resource")]
public string Resource { get; set; }
[XmlAttribute(AttributeName="size")]
public string Size { get; set; }
[XmlAttribute(AttributeName="checksum")]
public string Checksum { get; set; }
}
[XmlRoot(ElementName="source")]
public class Source {
[XmlElement(ElementName="file")]
public List<File> File { get; set; }
[XmlAttribute(AttributeName="uri")]
public string Uri { get; set; }
}
[XmlRoot(ElementName="manifest")]
public class Manifest {
[XmlElement(ElementName="source")]
public Source Source { get; set; }
[XmlAttribute(AttributeName="version")]
public string Version { get; set; }
[XmlAttribute(AttributeName="totalbytes")]
public string Totalbytes { get; set; }
}
One could call that lazy or cheating, but I don't see the point in writing code that can be generated for me in a second. You might not always get perfect results, but it's a good starting point. For example, it uses string for all attribute types. If you're expecting all numeric values you could replace those with int or long.
Now you can deserialize like this:
var serializer = new XmlSerializer(typeof(Manifest), new XmlRootAttribute("manifest"));
using (var stream = System.IO.File.OpenRead("test.xml"))
{
var deserialized = (Manifest)serializer.Deserialize(stream);
}
Once you've got the data deserialized into something, the rest is much easier. You can either use the auto-generated models or map them to your own.
Using LINQ...
c#
void Main()
{
string fileName = #"e:\Temp\GamePatches.xml";
XDocument manifest = XDocument.Load(fileName);
string version = manifest.Root.Attribute("version").Value;
List<ManifestModel> manifestModel = new List<ManifestModel>();
foreach (XElement e in manifest.Descendants("file"))
{
manifestModel.Add(new ManifestModel() { Version = version
, Resource = (string)e.Attribute("resource").Value
, Size = (string)e.Attribute("size").Value
, Checksum = (string)e.Attribute("checksum").Value }
);
}
}
// Define other methods and classes here
public class ManifestModel
{
public string Version { get; set; }
public string Resource { get; set; }
public string Size { get; set; }
public string Checksum { get; set; }
}
I spent a lot of time working on a similar app that parsed through XML Schema, and I found the easiest way is to turn the XML Document into an XmlNodeList. From here you can use the SelectNodes and SelectSingleNodes to navigate through it. Take a look at this:https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlnode.selectnodes?view=netframework-4.8, but basically what you do is create an xpath string which selects the node you need. Here is some documentation on that: https://learn.microsoft.com/en-us/dotnet/standard/data/xml/select-nodes-using-xpath-navigation
I am trying to deserialise an xml file to c# classes. I used an online tool to generate the classes for me as the structure of the XML file is quite complex. This worked well except for the population of repeated items into a List property in the main class.
I am using DotNet 4.5, C# in WPF.
A simplified version of the xml file is as follows:
<orderMessage xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:gs1:ecom:order:xsd:3">
<order xmlns="">
<creationDateTime>2017-07-10T00:00:00</creationDateTime>
<documentStatusCode>ORIGINAL</documentStatusCode>
<documentActionCode>ADD</documentActionCode>
</order>
<order xmlns="">
<creationDateTime>2017-07-10T00:00:00</creationDateTime>
<documentStatusCode>ORIGINAL</documentStatusCode>
<documentActionCode>ADD</documentActionCode>
</order>
</orderMessage>
The classes that I am using are as below:
[XmlRoot(ElementName = "order")]
public class Order
{
[XmlElement(ElementName = "creationDateTime")]
public string CreationDateTime { get; set; }
[XmlElement(ElementName = "documentStatusCode")]
public string DocumentStatusCode { get; set; }
[XmlElement(ElementName = "documentActionCode")]
public string DocumentActionCode { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
}
[XmlRoot(ElementName = "orderMessage", Namespace = "urn:gs1:ecom:order:xsd:3")]
public class OrderMessage
{
[XmlElement(ElementName = "order")]
public List<Order> Order { get; set; }
[XmlAttribute(AttributeName = "xsd", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsd { get; set; }
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
}
The deserialization code is as follows:
XmlSerializer serializer = new XmlSerializer(typeof(OrderMessage));
StreamReader reader = new StreamReader(filename);
OrderMessage newOrderMessage;
try
{
newOrderMessage = (OrderMessage)serializer.Deserialize(reader);
}
catch (Exception e)
{
throw e;
}
reader.Close();
When I run the code it runs without error but I end up with an empty list. There were other structures in the xml (that were not repeated structures - therefore no list property) that populated without problem that I have omitted.
I have looked at a number of questions similar to mine but they seem to suggest the same method I am using.
I am unable to change the XML as it is from a third party.
I would greatly appreciate it if anyone could point me in the right direction.
BTW - I know that catching an error and then throwing it is of no use whatsoever but I just did that to add a breakpoint so I could look at the inner exceptions if there were any. I will make the error handling more meaningful once I have the process working.
The issue is your Order property - the namespace will be inherited from OrderMessage, so it is urn:gs1:ecom:order:xsd:3 when it should be empty. You must specify this explicitly.
You can also remove a bunch of the namespace related attributes from your model. This is all you need:
[XmlRoot("orderMessage", Namespace = "urn:gs1:ecom:order:xsd:3")]
public class OrderMessage
{
[XmlElement("order", Namespace = "")]
public List<Order> Orders { get; set; }
}
public class Order
{
[XmlElement("creationDateTime")]
public string CreationDateTime { get; set; }
[XmlElement("documentStatusCode")]
public string DocumentStatusCode { get; set; }
[XmlElement("documentActionCode")]
public string DocumentActionCode { get; set; }
}
As an aside, throw e; in your deserialisation code is probably not what you want to do (see this question). Given you're not actually handling the exception, you can remove the try / catch entirely in this case.
You should also enclose your StreamReader in a using block to ensure it is disposed after use.
I´ve got a class called "server" with all attributes. I want to fill in the data from each node/element into the class.
The only way I know is foreach and than everytime a big switch-case. This can´t be the best way!
Here the XML-File:
<serverData .....>
<name>...</name>
<number>...</number>
<language>de</language>
<timezone>...</timezone>
<domain>...</domain>
<version>...</version>
...
</serverData>
The XML-File is from an API and I get it with this lines:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(Request.request(URL));
And now I want do something like (no real code just an example):
Server server = new Server();
server.name = xmlDoc.node["name"].Value;
server.version = ...
...
Thank you for your solution.
You can use LINQ to XML:
XDocument xDoc = XDocument.Parse(Request.request(URL));
Server server = new Server {
name = xDoc.Root.Element("name").Value,
number = int.Parse(xDoc.Root.Element("name").Value),
language = xDoc.Root.Element("language").Value,
timezone = xDoc.Root.Element("timezone").Value
/* etc. */
};
Since you have a well-formatted XML file with a constant structure, you can also simply serialize it using XmlSerializer:
[Serializable]
[XmlRoot("serverData")]
public class ServerData
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("number")]
public int Number { get; set; }
[XmlElement("language")]
public string Language { get; set; }
[XmlElement("timezone")]
public string Timezone { get; set; }
/* ... */
}
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ServerData));
using (Stream s = GenerateStreamFromString(Request.request(URL)))
{
xmlSerializer.Deserialize(s);
}
GenerateStreamFromString implementation can be found here.
First off, I followed the answer given here, but I still can not get the following to work.
I am retrieving XML from a web API, and the results returned are as such:
<ArrayOf__ptd_student_charges
xmlns="http://schemas.datacontract.org/2004/07/something.something"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<__ptd_student_charges>
<accumulated_tuition>000.000</accumulated_tuition>
<course_id>AAA-000/L</course_id>
<invoice_date>01/01/2015</invoice_date>
<lecturer_name>John Doe</lecturer_name>
<net_tuition>000.000</net_tuition>
<section_no>1</section_no>
<semester>Summer</semester>
<student_id>123456</student_id>
<student_name>John Doe</student_name>
<year>2015</year>
</__ptd_student_charges>
<__ptd_student_charges>
<accumulated_tuition>000.000</accumulated_tuition>
<course_id>AAA-000/L</course_id>
<invoice_date>01/01/2015</invoice_date>
<lecturer_name>John Doe</lecturer_name>
<net_tuition>000.000</net_tuition>
<section_no>1</section_no>
<semester>Summer</semester>
<student_id>123456</student_id>
<student_name>John Doe</student_name>
<year>2015</year>
</__ptd_student_charges>
</ArrayOf__ptd_student_charges>
I'm trying to deserialize this into an array of students.
My student class is defined like this:
public class Student
{
[System.Xml.Serialization.XmlElement("accumulated_tuiton")]
public double AccumulatedTution { get; set; }
[System.Xml.Serialization.XmlElement("net_tuiton")]
public double NetTuiton { get; set; }
[System.Xml.Serialization.XmlElement("course_id")]
public string CourseID { get; set; }
[System.Xml.Serialization.XmlElement("invoice_date")]
public DateTime InvoiceDate { get; set; }
[System.Xml.Serialization.XmlElement("lecturer_name")]
public string LecturerName { get; set; }
[System.Xml.Serialization.XmlElement("semester")]
public string Semester { get; set; }
[System.Xml.Serialization.XmlElement("student_id")]
public string StudentId { get; set; }
[System.Xml.Serialization.XmlElement("student_name")]
public string StudentName { get; set; }
[System.Xml.Serialization.XmlElement("year")]
public string Year { get; set; }
[System.Xml.Serialization.XmlElement("section_no")]
public int Section { get; set; }
}
And my student collection is defined like this:
[System.Xml.Serialization.XmlRoot("ArrayOf__ptd_student_charges xmlns=\"http://schemas.datacontract.org/2004/07/something.something\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance")]
public class StudentCollection
{
[XmlArray("ArrayOf__ptd_student_charges")]
[XmlArrayItem("__ptd_student_charges", typeof(Student))]
public Student[] StudentArray { get; set; }
}
I'm deserializing the results using this code:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
StudentCollection collection;
HttpWebRequest request = WebRequest.Create(stringUrl) as HttpWebRequest;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
XmlTextReader reader = new XmlTextReader(response.GetResponseStream());
XmlSerializer serializer = new XmlSerializer(typeof(StudentCollection));
collection = (StudentCollection)serializer.Deserialize(reader);
reader.Close();
}
Once I run this, I get an InvalidOperationException with a message
ArrayOf__ptd_student_charges
xmlns='http://schemas.datacontract.org/2004/07/something.something'>
was not expected.
I know that the xmlns:... shouldn't be in the first tag, but unfortunately it is and I'm unsure on how to proceed.
Basically, you need to support the default XML namespace in your XML file - you can either do this by specifying it on the StudentCollection:
[System.Xml.Serialization.XmlRoot("ArrayOf__ptd_student_charges")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.datacontract.org/2004/07/something.something", IsNullable = false)]
public class StudentCollection
{
[XmlArray("ArrayOf__ptd_student_charges")]
[XmlArrayItem("__ptd_student_charges", typeof(Student))]
public Student[] StudentArray { get; set; }
}
and the actual Student class:
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.datacontract.org/2004/07/something.something", IsNullable = false)]
public class Student
{
..........
}
or you can specify it programmatically when you deserialize:
XmlSerializer serializer = new XmlSerializer(typeof(StudentCollection),
"http://schemas.datacontract.org/2004/07/something.something");
That second parameter for the XmlSerializer is the default XML namespace to use when deserializing the XML content.
Extra tipp: if you ever have an XML file again, and you need to get the C# code classes that represent that XML - if you have Visual Studio 2012 or newer, just create a new code class, copy your XML file into the clipboard, and then use Edit > Paste Special > Paste XML as classes and you get all your C# including all XML attribute and XML namespaces and everything pasted into your Visual Studio right there
below is the XML code.
<Shops>
<Shop>
<Location>INDIA</Location>
<Id>123</Id>
<ShopLists>
<ShopList>
<Area>500sqft</Area>
<Name>Home Decor</Name>
<LicenseNo>Ab123</LicenseNo>
</ShopList>
<ShopList>
<Area>1000sqft</Area>
<LicenseNo>Ab123</LicenseNo>
</ShopList>
</ShopLists>
</Shop>
</Shops>
Creating an object with C# using Linq is finding challenging here as one of the data is missing in 'shoplist' and structure is nested. reply if find some inputs on this.
I encourage you to look at http://xmltocsharp.azurewebsites.net/ put your xml and you will be able to convert your xml representation into C# classes.
you can then use XmlSerializer to deserialize your xml into specific type as exemplified in here.
Hope that helps.
I always use XmlSerializer with objects to perform such tasks.
Reference Assembly System.Xml.Serialization
using System.Xml.Serialization;
First create the object model:
[XmlRoot("Shops")]
public class XmlShops
{
[XmlElement("Shop",typeof(Shop))]
public List<Shop> Shops { get; set; }
}
public class Shop
{
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Id")]
public string Id { get; set; }
[XmlArray("ShopLists")]
[XmlArrayItem("ShopList",typeof(ShopList))]
public List<ShopList> ShopLists { get; set;}
}
public class ShopList
{
[XmlElement("Area")]
public string Area { get;set; }
[XmlElement("Home")]
public string Home { get;set; }
[XmlElement("LicenseNo")]
public string LicenseNo { get;set; }
}
Afterwards use the Serializer to get the xml data into the object model:
XmlSerializer ser = new XmlSerializer(typeof(XmlShops));
using (StreamReader sr = new StreamReader(#"d:\tmp\test.xml"))
{
XmlShops data = (XmlShops)ser.Deserialize(sr);
// xml should be serialized to your object model into data.
}