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.
Related
I cant get the following XML deserialized because of the "type" attribute.
What I've tried so far:
The XML I want to deserialize:
<foundEntities>
<staticGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns1:StaticGroupType">
<number>10000</number>
<name>Gruppe A</name>
</staticGroup>
</foundEntities>
The Class:
[XmlRootAttribute(Namespace = "", IsNullable = false, ElementName = "foundEntities")]
public class FoundEntities
{
[XmlElement(ElementName = "staticGroup", Namespace = "")]
public StaticGroup staticGroup { get; set; }
}
[XmlRoot(ElementName = "staticGroup")]
[XmlInclude(typeof(StaticGroupType))]
public class StaticGroup
{
[XmlElement(ElementName = "number")]
public string number { get; set; }
[XmlElement(ElementName = "name")]
public string name { get; set; }
}
public class StaticGroupType : StaticGroup
{
}
Probably I am blind but what do I miss here? Any hint is highly appreciated.
I have generated the classes using https://xmltocsharp.azurewebsites.net/ and it gave me the following output:
[XmlRoot(ElementName = "staticGroup")]
public class FoundStaticGroup
{
[XmlElement(ElementName = "number")]
public string Number { get; set; }
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "type", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Type { get; set; }
}
[XmlRoot(ElementName = "foundEntities")]
public class FoundGroupEntities
{
[XmlElement(ElementName = "staticGroup")]
public StaticGroup StaticGroup { get; set; }
}
But what I get now is an error as already had a few times before:
"The specified type was not recognized: name='StaticGroupType', namespace='http://com.f24.soap.fwi.schema', at ."
I have posted a working solution here:
DotNetFiddle Link
I'm not sure if there are external schemas but I had to remove xsi:type="ns1:StaticGroupType" to get it to work. Your XML won't parse in most readers unless there is a definition for that namespace in the XML or a schema. Not knowing what your backend service is, I can't recommend how to resolve this.
The biggest issue is you had both your classes marked with the XmlRootAtribute code attribute. Only one class can represent the root in your example. I also removed unnecessary attributes like Namespace that don't have any impact since you are using the default namespace.
Update
The issue here is with the incoming XML it is not complete, it needs a schema reference to be able to resolve the StaticGroup type. You will have to contact the people on the backend to fix this, or strip that part of the XML manually. Correct XML would be as shown below:
<foundEntities
xsi:schemaLocation="http://com.f24.soap.fwi.schema (url to back-end schema here)">
<staticGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns1:StaticGroupType">
<number>10000</number>
<name>Gruppe A</name>
</staticGroup>
</foundEntities>
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
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
I have some XML with an element like this:
<hour base_forecast="12" datim="29/0" />
And am receiving the error:
Unexpected node type Element. ReadElementString method can only be
called on elements with simple or empty content.
I am guessing this is because the element has no value. I don't control this XML so I can't change it. How would I deserialize this?
** EDIT **
One of the attributes' values is ">6" .... could this be the culprit? If so, how do I handle that?
** Update **
Found some data that wasn't returning a > in a value of the attribute. Same error is occurring.
** Edit #3 *
Created an XSD for the XML I am receiving, then generated classes for them with the xsd tool. Adding to the bottom of this post.
Here is the Deserialization code:
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("xxx");
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
WeatherData Result = new WeatherData();
using (Stream st = resp.GetResponseStream())
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "model_data";
xRoot.IsNullable = true;
Result = new XmlSerializer(typeof(WeatherData), xRoot).Deserialize(st) as WeatherData; ** Error here
Xml returned:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE observation SYSTEM "http://private.com/hithere.dtd">
<model_data>
<site a="28/12" b="KXXX">
<hour x="-9999" y="-9999" z="-9999"/>
</site>
</model_data>
Data object
[Serializable, XmlRoot("model_data")]
public class WeatherData
{
[XmlElement("site")]
public string City { get; set; }
[XmlAttribute]
public string a { get; set; }
[XmlAttribute]
public string b { get; set; }
[XmlElement(ElementName="hour", IsNullable=true)]
public string Hour { get; set; }
[XmlAttribute]
public string x { get; set; }
[XmlAttribute]
public string y { get; set; }
[XmlAttribute]
public string z { get; set; }
}
XSD Tool generated classes
**Removed generated classes, but they are similar to what Hugo posted **
Looking at this part:
[XmlElement("site")]
public string City { get; set; }
<Site> contains <Hour>, so it is not an element with simple or empty content, I guess?
Edit: actually the whole thing seems suspect.
The data object seems to disregard all hierarchy information in the xml.
How about something like this?
[Serializable, XmlRoot("model_data")]
public class WeatherData
{
[XmlElement("site")]
public City[] City { get; set; }
}
public class City
{
[XmlAttribute]
public string a { get; set; }
[XmlAttribute]
public string b { get; set; }
[XmlElement(ElementName="hour", IsNullable=true)]
public Hour Hour { get; set; }
}
public class Hour
{
[XmlAttribute]
public string x { get; set; }
[XmlAttribute]
public string y { get; set; }
[XmlAttribute]
public string z { get; set; }
}
When I generated the classes with the XSD tool, I getting the same error, but the error was showing being thrown from a line I had commented out.
So I exited VS, and ran disk cleanup. Ran my code again. Received a message "For security reasons DTD is prohibited in this XML document. etc." So, I allowed set the reader to DtdProcessing.Parse, and ran the code once more.
It was successful.
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.