Yes, I have read other threads on this subject but I am missing something:
I am trying to deserialize an XML document that, in part, contains logged SMS messages. The XML file takes the format of:
<reports>
<report>
<sms_messages>
<sms_message>
<number>4155554432</number>
<text>Hi! How are you?</text>
</sms_message>
<sms_message>
<number>4320988876</number>
<text>Hello!</text>
</sms_message>
</sms_messages>
</report>
</reports>
My code looks like:
[XmlType("sms_message")]
public class SMSMessage
{
[XmlElement("number")]
public string Number {get;set;}
[XmlElement("text")]
public string TheText {get;set;}
}
[XmlType("report")]
public class AReport
{
[XmlArray("sms_messages")]
public List<SMSMessage> SMSMessages = new List<SMSMessage>();
}
[XmlRoot(Namespace="www.mysite.com", ElementName="reports", DataType="string", IsNullable=true)]
public class AllReports
{
[XmlArray("reports")]
public List<AReport> AllReports = new List<AReport>();
}
I am trying to serialize it like this:
XmlSerializer deserializer = new XmlSerializer(typeof(AllReports));
TextReader tr = new StreamReader(this.tbXMLPath.text);
List<AllReports> reports;
reports = (List<AllReports>)deserializer.Deserialize(tr);
tr.close();
I get the error: "There is an error in XML document (2,2)" The inner exception states,<reports xmlns=''> was not expected.
I am sure it has something to do with the serializer looking for the namespace of the root node? Is my syntax correct above with the [XmlArray("reports")] ? I feel like something is amiss because "reports" is the root node and it contains a list of "report" items, but the decoration for the root node isn't right? This is my first foray into this area. Any help is greatly appreciated.
With a minimal change to your non-compilable code
XmlSerializer deserializer = new XmlSerializer(typeof(AllReports));
TextReader tr = new StreamReader(filename);
AllReports reports = (AllReports)deserializer.Deserialize(tr);
[XmlType("sms_message")]
public class SMSMessage
{
[XmlElement("number")]
public string Number { get; set; }
[XmlElement("text")]
public string TheText { get; set; }
}
[XmlType("report")]
public class AReport
{
[XmlArray("sms_messages")]
public List<SMSMessage> SMSMessages = new List<SMSMessage>();
}
[XmlRoot("reports")]
public class AllReports
{
[XmlElement("report")]
public List<AReport> Reports = new List<AReport>();
}
Related
I need to deserialize XML that uses a field "type" to indicate what content to expect.
Type 0 says that I can expect simple text whilst type 1 indicates that the content is of a more complex structure.
I know that I could write some custom deserialization mechanism but would like to know whether there was any builtin way to solve this.
Since the XMLSerializer expects a string it simply throws away the content in case it is XML. This stops me from running the content deserialization as a second step.
<Msg>
<MsgType>0</MsgType>
<Data>Some text</Data>
</Msg>
<Msg>
<MsgType>1</MsgType>
<Data>
<Document>
<Type>PDF</Type>
.....
</Document>
</Data>
</Msg>
That isn't supported out of the box; however, you could perhaps use:
public XmlNode Data {get;set;}
and run the "what to do with Data?" as a second step, once you can look at MsgType.
Complete example:
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
static class P
{
static void Main()
{
const string xml = #"<Foo>
<Msg>
<MsgType>0</MsgType>
<Data>Some text</Data>
</Msg>
<Msg>
<MsgType>1</MsgType>
<Data>
<Document>
<Type>PDF</Type>
.....
</Document>
</Data>
</Msg>
</Foo>";
var fooSerializer = new XmlSerializer(typeof(Foo));
var docSerializer = new XmlSerializer(typeof(Document));
var obj = (Foo)fooSerializer.Deserialize(new StringReader(xml));
foreach (var msg in obj.Messages)
{
switch (msg.MessageType)
{
case 0:
var text = msg.Data.InnerText;
Console.WriteLine($"text: {text}");
break;
case 1:
var doc = (Document)docSerializer.Deserialize(new XmlNodeReader(msg.Data));
Console.WriteLine($"document of type: {doc.Type}");
break;
}
Console.WriteLine();
}
}
}
public class Foo
{
[XmlElement("Msg")]
public List<Message> Messages { get; } = new List<Message>();
}
public class Message
{
[XmlElement("MsgType")]
public int MessageType { get; set; }
public XmlNode Data { get; set; }
}
public class Document
{
public string Type { get; set; }
}
I'm working with some third party XML that has no formally defined schema, only example XML. I have several thousand XML files from this third party. There is no guarantee that every possible element lies within one or more of these files. The third party service could send me a new file with a new element!
I can view these files and reverse engineer types relatively easily.
For example:
<MyObject>
<MyProperty>Some value</MyProperty>
</MyObject>
Could deserialize to
public class MyObject
{
public string MyProperty { get; set; }
}
No problems so far.
But what if I attempt to deserialize this:
<MyObject>
<MyProperty>Some value</MyProperty>
<MyOtherProperty>Some value</MyOtherProperty>
</MyObject>
into my class above? I want it to throw an exception, so I can be notified that my class does not accommodate MyOtherProperty.
Is there a way to do this?
I'd like to share the code I wrote using the accepted answer. Using the below utility method, I can deserialize without checks for unknown stuff, and with checks, by setting strict=true.
I hope readers find this useful!
public static T XmlDeserialize<T>(string xml, bool strict = false)
{
using (var stringReader = new StringReader(xml))
{
using (var xmlTextReader = new XmlTextReader(stringReader))
{
var xmlSerializer = new XmlSerializer(typeof(T));
if (strict)
{
var options = new XmlDeserializationEvents();
options.OnUnknownElement += (sender, args) =>
{
throw new XmlDeserializationException(xml, $"Unexpected Element {args.Element.LocalName} on line {args.LineNumber}.");
};
options.OnUnknownAttribute += (sender, args) =>
{
throw new XmlDeserializationException(xml, $"Unexpected Element: {args.Attr.LocalName} on line {args.LineNumber}.");
};
options.OnUnknownNode += (sender, args) =>
{
throw new XmlDeserializationException(xml, $"Unexpected Element: {args.LocalName} on line {args.LineNumber}.");
};
return (T)xmlSerializer.Deserialize(xmlTextReader, options);
}
return (T)xmlSerializer.Deserialize(xmlTextReader);
}
}
}
And that exception class I'm throwing looks like this:
public class XmlDeserializationException : Exception
{
public string Xml { get; private set; }
public XmlDeserializationException(
string xml, string message) : base (message)
{
Xml = xml;
}
}
I can check my logs and look up the line number in the actual xml. Works perfectly. Thanks, pfx.
The XmlSerializer has a Deserialize overload allowing to pass in an options element by which to hook to some events; eg. OnUnknownElement.
XmlDeserializationEvents options = new XmlDeserializationEvents();
options.OnUnknownElement += (sender, args) => {
XmlElement unknownElement = args.Element;
// throw an Exception with this info.
} ;
var o = serializer.Deserialize(xml, options) as MyObject;
The OnUnknowElement takes nested elements into account.
With the classes below
public class MyObject
{
public string MyProperty { get; set; }
public MyOtherObject Other { get; set; }
}
public class MyOtherObject
{
public string SomeProperty { get; set; }
}
and the following xml
<MyObject>
<MyProperty>Some value</MyProperty>
<Other>
<SomeProperty>...</SomeProperty>
<UFO>...</UFO>
</Other>
</MyObject>
The OnUnknowElement handler will trigger for the UFO element.
One way of doing so would be to use your current object model and create an XSD out of it. You can then check new files against that XSD and throw if it doesn't validate.
Extend your class with an XmlAnyElement container.
Any unknown elements will end up in that array.
After deserialization check whether that array is empty.
public class MyObject
{
[XmlAnyElement]
public XmlElement[] UnknownElements;
public string MyProperty { get; set; }
}
While creating xml from C# class I getting some default namespaces(xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema") in root tag (Order) like below. but, I want to remove those default namespaces and I need the following namespace in the root tag (Order xmlns="http://example.com/xml/1.0").
how to remove those default namespaces and replace in c# code. thanks in advance.
<?xml version="1.0"?>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Number Type="mobile">9999999999</Number>
<TrackStartDateTime>2015-05-30 11:00 ET</TrackStartDateTime>
<Notifications>
<Notification>
<PartnerMPID>99999999</PartnerMPID>
<IDNumber>L563645</IDNumber>
<TrackDurationInHours>120</TrackDurationInHours>
<TrackIntervalInMinutes>240</TrackIntervalInMinutes>
</Notification>
</Notifications>
<Carrier>
<Dispatcher>
<DispatcherName>?</DispatcherName>
<DispatcherPhone>0</DispatcherPhone>
<DispatcherEmail>?</DispatcherEmail>
</Dispatcher>
</Carrier>
</Order>
I have used following C# classes.
[XmlRoot("Order")]
public class Order
{
[XmlElement("Number")]
public Number Number;
[XmlElement("TrackStartDateTime")]
public string TrackStartDateTime;//TODO - need to check
[XmlElement("Notifications")]
public Notifications Notifications;//TODO - notification tag should come inside Notifications tag
[XmlElement("Carrier")]
public Carrier Carrier;
public Order() {
Number = new Number();
Notifications = new Notifications();
Carrier = new Carrier();
TripSheet = new TripSheet();
}
}
public class Number
{
[XmlAttribute("Type")]
public string Type;
[XmlText]
public Int64 Value;
}
public class Notifications {
[XmlElement("Notification")]
public List<Notification> Notification;
public Notifications() {
Notification = new List<Notification>();
}
}
public class Notification
{
[XmlElement("PartnerMPID")]
public string PartnerMPID { get; set; }
[XmlElement("IDNumber")]
public string IDNumber { get; set; }
[XmlElement("TrackDurationInHours")]
public int TrackDurationInHours { get; set; }
[XmlElement("TrackIntervalInMinutes")]
public int TrackIntervalInMinutes { get; set; }
}
public class Carrier
{
[XmlElement("Name")]
public string Name;
[XmlElement("Dispatcher")]
public Dispatcher Dispatcher;
public Carrier() {
Dispatcher = new Dispatcher();
}
}
public class Dispatcher
{
[XmlElement("DispatcherName")]
public string DispatcherName;
[XmlElement("DispatcherPhone")]
public Int64 DispatcherPhone;
[XmlElement("DispatcherEmail")]
public string DispatcherEmail;//conform format for email
}
and I have taken the new instance of Order Class and for testing purpose, I have hard-coded values for the each fields and I have used the following code for the creating xml from the C# class.
public string CreateXML(Order order)
{
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Order));
// Creates a stream whose backing store is memory.
using (MemoryStream xmlStream = new MemoryStream())
{
xmlSerializer.Serialize(xmlStream, order);//,ns
xmlStream.Position = 0;
//Loads the XML document from the specified string.
xmlDoc.Load(xmlStream);
return xmlDoc.InnerXml;
}
}
I am not sure its a right approach for creating xml from C# classes. Please guide me to get the following xml output from c# class.
<?xml version="1.0"?>
<Order xmlns="http://example.com/xml/1.0" >
<Number Type="mobile">9999999999</Number>
<TrackStartDateTime>2015-05-30 11:00 ET</TrackStartDateTime>
<Notifications>
<Notification>
<PartnerMPID>99999999</PartnerMPID>
<IDNumber>L563645</IDNumber>
<TrackDurationInHours>120</TrackDurationInHours>
<TrackIntervalInMinutes>240</TrackIntervalInMinutes>
</Notification>
</Notifications>
<Carrier>
<Dispatcher>
<DispatcherName>?</DispatcherName>
<DispatcherPhone>0</DispatcherPhone>
<DispatcherEmail>?</DispatcherEmail>
</Dispatcher>
</Carrier>
</Order>
Here is a way to do it...
Just create a new XDocument and set the namespace that you want on it and transplant the original xml descendant into it, like so:
var xml = "<?xml version=\"1.0\"?><Order xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Number Type=\"mobile\">9999999999</Number></Order>";
var xdoc = XDocument.Parse(xml);
var ns = XNamespace.Get("http://example.com/xml/1.0");
var xdoc2 = new XDocument(new XDeclaration("1.0", null, null),
new XElement(ns + "Order", xdoc.Root.Descendants()));
See here for working sample: https://dotnetfiddle.net/JYCL95
For this case it should be sufficient to simply add a null namespace to the XMLRoot decoration used on your class definition.
[XmlRoot("Order", Namespace = null)]
public class Order
{
[XmlElement("Number")]
public Number Number;
[XmlElement("TrackStartDateTime")]
public string TrackStartDateTime;
[XmlElement("Notifications")]
public Notifications Notifications;
[XmlElement("Carrier")]
public Carrier Carrier;
The serializer should do the rest.
I've been landed with a feed of XML data that I need to deserialise into objects in a Silverlight (v5) application. The data looks like:
<AgentState>
<agentName>jbloggs</agentName>
<extension>12345</extension>
<currentlyIn>TestStatus</currentlyIn>
</AgentState>
I've created a class at the Silverlight side, and I'm trying to get this XML - which, you'll notice, is missing a declaration and a namespace - into objects.
StringReader sr = null;
string data = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
sr = new StringReader(data);
XmlSerializer xs = new XmlSerializer(typeof (AgentState));
AgentState agent = (AgentState) xs.Deserialize(sr);
.. but this throws an error an error in xml document (1,2), as it's missing the declaration. Even manually adding a dummy declaration gives further errors about missing namespaces.
I've found other questions about ignoring namespace/declarations in XML, but none of these seem to work in Silverlight.
Can anyone advise on the best way to get this XML deserialised into an object?
This seems to work:
public class AgentState
{
public string agentName { get; set; }
public string extension { get; set; }
public string currentlyIn { get; set; }
}
static void Main(string[] args)
{
var s = #"<AgentState>
<agentName>jbloggs</agentName>
<extension>12345</extension>
<currentlyIn>TestStatus</currentlyIn>
</AgentState>";
XmlSerializer serializer = new XmlSerializer(typeof(AgentState));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(s));
var obj = serializer.Deserialize(ms);
}
I'm wondering what issue you have with appending the xml declaration to the string. This appears to work ok:
[System.Xml.Serialization.XmlRootAttribute("AgentState")]
public class AgentState
{
public string agentName {get; set;}
public int extension {get; set;}
public string currentlyIn {get; set;}
}
public void RunSerializer()
{
System.Xml.Serialization.XmlSerializer agent_serializer =
new System.Xml.Serialization.XmlSerializer(typeof(AgentState));
string agent_state_text = File.ReadAllText(#"C:\Temp\AgentState.xml");
Console.WriteLine(agent_state_text + Environment.NewLine);
string xml_agent_state = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + agent_state_text;
Console.WriteLine(xml_agent_state + Environment.NewLine);
AgentState agent_state = new AgentState();
using(StringReader tx_reader = new StringReader(xml_agent_state))
{
if (tx_reader != null)
{
agent_state = (AgentState)agent_serializer.Deserialize(tx_reader);
}
}
Console.WriteLine(agent_state.agentName);
Console.WriteLine(agent_state.extension);
Console.WriteLine(agent_state.currentlyIn);
}
Output:
<AgentState>
<agentName>jbloggs</agentName>
<extension>12345</extension>
<currentlyIn>TestStatus</currentlyIn>
</AgentState>
<?xml version="1.0" encoding="UTF-8"?>
<AgentState>
<agentName>jbloggs</agentName>
<extension>12345</extension>
<currentlyIn>TestStatus</currentlyIn>
</AgentState>
jbloggs
12345
TestStatus
I've managed to get it working using the following code - I'm not convinced it's the "right" way to do things, but it seems to work:
string data = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
var document = XDocument.Parse(data);
AgentState agent= (from c in document.Elements()
select new AgentState()
{
agentName = c.Element("agentName").Value,
extension = c.Element("extension").Value,
currentlyIn=c.Element("currentlyIn").Value
}).Single();
Thanks for the advice, it got me on the right track.
I have next XML file:
<Root>
<Document>
<Id>d639a54f-baca-11e1-8067-001fd09b1dfd</Id>
<Balance>-24145</Balance>
</Document>
<Document>
<Id>e3b3b4cd-bb8e-11e1-8067-001fd09b1dfd</Id>
<Balance>0.28</Balance>
</Document>
</Root>
I deserialize it to this class:
[XmlRoot("Root", IsNullable = false)]
public class DocBalanceCollection
{
[XmlElement("Document")]
public List<DocBalanceItem> DocsBalanceItems = new List<DocBalanceItem>();
}
where DocBalanceItem is:
public class DocBalanceItem
{
[XmlElement("Id")]
public Guid DocId { get; set; }
[XmlElement("Balance")]
public decimal? BalanceAmount { get; set; }
}
Here is my deserialization method:
public DocBalanceCollection DeserializeDocBalances(string filePath)
{
var docBalanceCollection = new DocBalanceCollection();
if (File.Exists(filePath))
{
var serializer = new XmlSerializer(docBalanceCollection.GetType());
TextReader reader = new StreamReader(filePath);
docBalanceCollection = (DocBalanceCollection)serializer.Deserialize(reader);
reader.Close();
}
return docBalanceCollection;
}
All works fine but I have many XML files. Besides writing Item classes I have to write ItemCollection classes for each of them. And also I have to implement DeserializeItems method for each.
Can I deserialize my XML files without creating ItemCollection classes? And can I write single generic method to deserialize all of them?
The only solution that comes to mind - make an interface for all these classes. Any ideas?
You can deserialize a generic List<T> just fine with XmlSerializer. However, first you need to add the XmlType attribute to your DocBalanceItem so it knows how the list elements are named.
[XmlType("Document")]
public class DocBalanceItem
{
[XmlElement("Id")]
public Guid DocId { get; set; }
[XmlElement("Balance")]
public decimal? BalanceAmount { get; set; }
}
Then modify your DeserializeDocBalances() method to return a List<T> and pass the serializer an XmlRootAttribute instance to instruct it to look for Root as the root element:
public List<T> DeserializeList<T>(string filePath)
{
var itemList = new List<T>();
if (File.Exists(filePath))
{
var serializer = new XmlSerializer(typeof(List<T>), new XmlRootAttribute("Root"));
TextReader reader = new StreamReader(filePath);
itemList = (List<T>)serializer.Deserialize(reader);
reader.Close();
}
return itemList;
}
Then you should be able to do
var list = DeserializeList<DocBalanceItem>("somefile.xml");
Since the method now returns a generic List<T>, you no longer need to create custom collections for every type.
P.S. - I tested this solution locally with the provided document, it does work.
Any stringable object can be deserialized by following method.
public static T genericDeserializeSingleObjFromXML<T>(T value, string XmalfileStorageFullPath)
{
T Tvalue = default(T);
try
{
XmlSerializer deserializer = new XmlSerializer(typeof(T));
TextReader textReader = new StreamReader(XmalfileStorageFullPath);
Tvalue = (T)deserializer.Deserialize(textReader);
textReader.Close();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("serialization Error : " + ex.Message);
}
return Tvalue;
}
In order to use this method you should already serialize the object in xml file.
Calling method is :
XmlSerialization.genericDeserializeSingleObjFromXML(new ObjectName(), "full path of the XML file");