c# loop through xml child nodes and deserialize - c#

I'm trying to loop into an XmlDocument to serialize objects. Let's suppose an simple xml :
<?xml version="1.0" encoding="iso-8859-15"?>
<root>
<message>
<id>1</id>
<text>test</text>
</message>
<message>
<id>2</id>
<text>test 2</text>
</message>
</root>
So this is my c# program :
class Program
{
static void Main(string[] args)
{
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.IgnoreComments = true;
XmlSerializer serializer = new XmlSerializer(typeof(Message));
XmlReader xmlReader = XmlReader.Create(#"..\..\test.xml");
XmlDocument doc = new XmlDocument();
doc.Load(xmlReader);
foreach(XmlElement element in doc.DocumentElement.ChildNodes)
{
Console.WriteLine($"id : {element.SelectSingleNode("id").InnerText}, message : {element.SelectSingleNode("text").InnerText}");
Message message = (Message)serializer.Deserialize(XmlReader.Create(element.OuterXml.ToString()));
}
Console.ReadLine();
}
}
public class Message
{
public int id;
public string text;
}
but i got an error Illegal characters in path, but the print is okay, what's wrong ? and is there a way to serialize directly the XmlElement without go through the tostring() ?

Ok, i found the problem. The serializer is looking for a tag named Message like the name of the class but the tag was message with an lowercase m. It is still possible to differentiate the name of the class and the label of the tag and associate them with a decorator as :
[XmlRoot(ElementName = "message")]
public class Message
{
public int id { get; set; }
public string? text { get; set; }
}

Related

C# serialize object to XML with element containing XML text without escaping

I searched and tried some attributes but none of them worked for my below scenario:
public class ObjSer
{
[XmlElement("Name")]
public string Name
{
get; set;
}
}
//Code to serialize
var obj = new ObjSer();
obj.Name = "<tag1>Value</tag1>";
var stringwriter = new System.IO.StringWriter();
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(stringwriter, obj);
Output would be as follows:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
But I need output as:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
Scenario 2: In some cases I need to set:
obj.Name = "Value";
Is there any attribute or method I can override to make it possible?
You can't with default serializer. XmlSerializer does encoding of all values during serialization.
If you want your member to hold xml value, it must be XmlElement. Here is how you can accomplish it
public class ObjSer
{
[XmlElement("Name")]
public XmlElement Name
{
get; set;
}
}
var obj = new ObjSer();
// <-- load xml
var doc = new XmlDocument();
doc.LoadXml("<tag1>Value</tag1>");
obj.Name = doc.DocumentElement;
// --> assign the element
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
Output:
<?xml version="1.0" encoding="IBM437"?>
<ObjSer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>
<tag1>Value</tag1>
</Name>
</ObjSer>
UPDATE:
In case if you want to use XElement instead of XmlElement, here is sample on how to do it
public class ObjSer
{
[XmlElement("Name")]
public XElement Name
{
get; set;
}
}
static void Main(string[] args)
{
//Code to serialize
var obj = new ObjSer();
obj.Name = XDocument.Parse("<tag1>Value</tag1>").Document.FirstNode as XElement;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
}
No, you can't. It is the natural and usual behaviour of the xml serializer. The serializer doesn't have to handle within the XML strings. So, it escapes the xml as expected.
You should decode the escaped string again while deserializing it.
If you want to add dynamically elements in the XML I suggest you to use Linq to XML and you can create tag1 or another kind of elements easily.
I would suggest serializing to an XDocument then converting that to a string and manually unescaping the desired part and writing it to a file. I would say this would give you the least headache it shouldn't be more than a couple lines of code. If you need it I can provide some code example if needed.
I found one more way of changing the type
public class NameNode
{
public string tag1
{
get; set;
}
[XmlText]
public string Value
{
get; set;
}
}
public class ObjSer
{
[XmlElement("Name")]
public NameNode Name
{
get; set;
}
}
To set value of Name:
var obj = new ObjSer();
var valueToSet = "<tag1>Value</tag1>";
//or var valueToSet = "Value";
//With XML tag:
if (valueToSet.Contains("</"))
{
var doc = new XmlDocument();
doc.LoadXml(valueToSet);
obj.Name.tag1 = doc.InnerText;
}
else //Without XML Tags
{
obj.Name.Value = senderRecipient.Name;
}
This solution will work in both cases, but has limitation. It will work only for predefined tags (ex. tag1)

How set xmlns prefix in deserialization class for the root element

I used xmltocsharp site to create the class helper to deserialize an specific XML, but is not working, and the problem is in the root element. This is the root element (RESP_HDR and RESP_BODY were collapsed):
<?xml version="1.0" encoding="UTF-8"?>
<SII:RESPUESTA xmlns:SII="http://www.sii.cl/XMLSchema">
+ <SII:RESP_HDR>
+ <SII:RESP_BODY>
</SII:RESPUESTA>
And this is the root element class generated by xmltocsharp site:
[XmlRoot(ElementName = "RESPUESTA", Namespace = "http://www.sii.cl/XMLSchema")]
public class RESPUESTA
{
[XmlElement(ElementName = "RESP_HDR", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_HDR RESP_HDR { get; set; }
[XmlElement(ElementName = "RESP_BODY", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_BODY RESP_BODY { get; set; }
[XmlAttribute(AttributeName = "SII", Namespace = "http://www.w3.org/2000/xmlns/")]
public string SII { get; set; }
}
The issue is that the class fails to deserialize a XML like the showed before, but success with this:
<?xml version="1.0" encoding="UTF-8"?>
<RESPUESTA xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SII="http://www.sii.cl/XMLSchema" xmlns="http://www.sii.cl/XMLSchema">
+ <SII:RESP_HDR>
+ <SII:RESP_BODY>
</RESPUESTA>
The difference is in the namespaces, even if a create the object and serialize it, this will be the result. So, what should be changed in the class to make it work with the original XML?
UPDATE:
Looking closer I found the really issue, is in the root element still, but I notice the missing xmlns prefix in the root tag, how can I set it in the helper class?
EDIT:
This is an XML sample from the service response:
<?xml version="1.0" encoding="UTF-8"?>
<SII:RESPUESTA xmlns:SII="http://www.sii.cl/XMLSchema">
<SII:RESP_HDR>
<SII:ESTADO>0</SII:ESTADO>
<SII:GLOSA/>
</SII:RESP_HDR>
<SII:RESP_BODY>
<DATOS_CONSULTA>
<RUT>80182144-3</RUT>
<TIPO_CONSULTA>DEUDOR</TIPO_CONSULTA>
<DESDE_DDMMAAAA>01042017</DESDE_DDMMAAAA>
<HASTA_DDMMAAAA>01052017</HASTA_DDMMAAAA>
</DATOS_CONSULTA>
<CESION>
<VENDEDOR>11455447-9</VENDEDOR>
<ESTADO_CESION>Cesion Vigente</ESTADO_CESION>
<DEUDOR>80182144-3</DEUDOR>
<MAIL_DEUDOR/>
<TIPO_DOC>33</TIPO_DOC>
<NOMBRE_DOC>Factura Electronica</NOMBRE_DOC>
<FOLIO_DOC>107</FOLIO_DOC>
<FCH_EMIS_DTE>2017-04-04</FCH_EMIS_DTE>
<MNT_TOTAL>3324860</MNT_TOTAL>
<CEDENTE>11455447-9</CEDENTE>
<RZ_CEDENTE>JHON DOE</RZ_CEDENTE>
<MAIL_CEDENTE>jjdoe#gmail.com</MAIL_CEDENTE>
<CESIONARIO>762327129-7</CESIONARIO>
<RZ_CESIONARIO>capital sa</RZ_CESIONARIO>
<MAIL_CESIONARIO>xcap#capital.com</MAIL_CESIONARIO>
<FCH_CESION>2017-04-05 13:15</FCH_CESION>
<MNT_CESION>3324860</MNT_CESION>
<FCH_VENCIMIENTO>2017-06-04</FCH_VENCIMIENTO>
</CESION>
<CESION>
<VENDEDOR>11455447-9</VENDEDOR>
<ESTADO_CESION>Cesion Vigente</ESTADO_CESION>
<DEUDOR>80182144-3</DEUDOR>
<MAIL_DEUDOR/>
<TIPO_DOC>33</TIPO_DOC>
<NOMBRE_DOC>Factura Electronica</NOMBRE_DOC>
<FOLIO_DOC>34</FOLIO_DOC>
<FCH_EMIS_DTE>2017-03-01</FCH_EMIS_DTE>
<MNT_TOTAL>1725500</MNT_TOTAL>
<CEDENTE>11455447-9</CEDENTE>
<RZ_CEDENTE>JOE DOE</RZ_CEDENTE>
<MAIL_CEDENTE>jd#gmail.com</MAIL_CEDENTE>
<CESIONARIO>762327129-7</CESIONARIO>
<RZ_CESIONARIO>Capital S.A.</RZ_CESIONARIO>
<MAIL_CESIONARIO>jcap#capital.com</MAIL_CESIONARIO>
<FCH_CESION>2017-04-05 17:27</FCH_CESION>
<MNT_CESION>1725500</MNT_CESION>
<FCH_VENCIMIENTO>2017-03-01</FCH_VENCIMIENTO>
</CESION>
</SII:RESP_BODY>
</SII:RESPUESTA>
So far the only way that I can make it work is with a really ugly solution, this should not be considered as an answer to the problem!!.
//Query service to obtain XML response
string xmlResponse = siiClient.QueryDocuments(documentsRequest);
//Replace RESPUESTA tags in the XML response, remove the prefix SII
var replacedXML = xmlResponse .Replace("SII:RESPUESTA", "RESPUESTA" );
//Load XML string into XmlDocument
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(replacedXML);
//Add missing namespaces
xDoc.DocumentElement.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
xDoc.DocumentElement.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
xDoc.DocumentElement.SetAttribute("xmlns", "http://www.sii.cl/XMLSchema");
//Now deserialization will work
var documentResponse = xDoc.ParseXML<RESPUESTA>();
The ideal solution will take the XML Response and deserialize it directly without any preprocessing like:
//Query service to obtain XML response
string xmlResponse = siiClient.QueryDocuments(documentsRequest);
//ParseXML is an extension method, it can handle an string or an XmlDocument
var documentResponse = xmlResponse.ParseXML<RESPUESTA>();
Note: ParseXML is based on #Damian's answer
I took your xml:
<?xml version="1.0" encoding="UTF-8"?>
<SII:RESPUESTA xmlns:SII="http://www.sii.cl/XMLSchema">
<SII:RESP_HDR/>
<SII:RESP_BODY/>
</SII:RESPUESTA>
I took you class:
[XmlRoot(ElementName = "RESPUESTA", Namespace = "http://www.sii.cl/XMLSchema")]
public class RESPUESTA
{
[XmlElement(ElementName = "RESP_HDR", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_HDR RESP_HDR { get; set; }
[XmlElement(ElementName = "RESP_BODY", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_BODY RESP_BODY { get; set; }
[XmlAttribute(AttributeName = "SII", Namespace = "http://www.w3.org/2000/xmlns/")]
public string SII { get; set; }
}
public class RESP_HDR { }
public class RESP_BODY { }
Just added two empty class stub.
Try System.Xml.Serialization.XmlSerializer:
RESPUESTA respuesta;
var xs = new XmlSerializer(typeof(RESPUESTA));
using (var fs = new FileStream("test.xml", FileMode.Open))
respuesta = (RESPUESTA)xs.Deserialize(fs);
It works! I don't understand how and why it does not work for you.

Deserialization from XML to List Object

I am doing the program in convert XML file to List Objects. I have successfully done serialization from List to XML .but I have an problem on doing deserialization. Please anyone tell me what's the wrong i have done in this code.
This is my XML code.
<?xml version="1.0"?>
<Contact_x0020_Form xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Contact>
<Contact>
<Id>1</Id>
<Name>vicky1kumar</Name>
<Phone>248847227</Phone>
</Contact>
<Contact>
<Id>2</Id>
<Name>vicky1kumar2kumar</Name>
<Phone>725228355</Phone>
</Contact>
<Contact>
<Id>3</Id>
<Name>vicky1kumar2kumar3kumar</Name>
<Phone>2032848116</Phone>
</Contact>
<Contact>
<Id>4</Id>
<Name>vicky1kumar2kumar3kumar4kumar</Name>
<Phone>853938969</Phone>
</Contact>
<Contact>
<Id>5</Id>
<Name>vicky1kumar2kumar3kumar4kumar5kumar</Name>
<Phone>530646891</Phone>
</Contact>
</Contact>
<Id>0</Id>
</Contact_x0020_Form>
This is my Class for convert XML to List Object
public class Converter
{
public static T XmlToObject<T>(string xml)
{
using (var xmlStream = new StringReader(xml))
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(XmlReader.Create(xmlStream));
}
}
public static List<T> XmlToObjectList<T>(string xml, string nodePath)
{
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
var returnItemsList = new List<T>();
foreach (XmlNode xmlNode in xmlDocument.SelectNodes(nodePath))
{
returnItemsList.Add(XmlToObject<T>(xmlNode.OuterXml));
}
return returnItemsList;
}
}
And this is my DEserialization code...
List<string> decont = new List<string>();
decont = Converter.XmlToObjectList<string>(#"C:\vignesh\serialization\xmlserialize\XmlSerializeContact.xml","//Contact");
foreach (var item in decont)
{
Console.WriteLine(decont);
}
I got this error:
Data at the root level is invalid. Line 1, position 1.
Data at the root level is invalid. Line 1, position 1.
To address this first error, you must understand the cause. The issue is LoadXml accepts an xml string; whereas you are passing a path to an Xml file. You need to use Load instead of LoadXml.
That said, there are a lot of other things you need to correct. The serialized XML provided in your question seems to be incorrect--e.g. the Contact node is its own parent. Thus, your node selection is giving you the entire Xml. (Did you mean to define Contacts node to be the parent of the Contact list?)
<Contacts>
.. <Contact>
First, your xml should look like this:
<?xml version="1.0"?>
<Contact_x0020_Form>
<Contacts>
<Contact>
<Id>1</Id>
<Name>vicky1kumar</Name>
<Phone>248847227</Phone>
</Contact>
<Contact>
<Id>2</Id>
<Name>vicky1kumar2kumar</Name>
<Phone>725228355</Phone>
</Contact>
</Contacts>
</Contact_x0020_Form>
Second, define serialization classes something like :
[XmlRoot(ElementName = "Contact_x0020_Form")]
public class Root
{
[XmlElement("Contacts")]
public Contacts contacts{get;set;}
}
public class Contacts
{
public List<Contact> contacts {get;set;}
}
public class Contact
{
[XmlElement("Id")]
public int Id {get;set;}
[XmlElement("Name")]
public string Name {get;set;}
[XmlElement("Phone")]
public string Phone {get;set;}
}
and your helper classes
public static class serialize
{
public static T Deserialize<T>(string path)
{
T result;
using (var stream = File.Open(path, FileMode.Open))
{
result = Deserialize<T>(stream);
}
return result;
}
public static void Serialize<T>(T root, string path)
{
using (var stream = File.Open(path, FileMode.Create))
{
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(stream, root);
}
}
public static T Deserialize<T>(Stream stream)
{
var xmlSerializer = new XmlSerializer(typeof(T));
return (T)xmlSerializer.Deserialize(stream);
}
}
And, finally your func:
static void Main()
{
var a = serialize.Deserialize<Root>("input.xml"); //xml file name given here.
Console.WriteLine(a.contacts);
}
This is how you should proceed. Then on, you can get the list of objects that you want.
use this before calling xml data in deserialization
xml = Regex.Replace(xml, "<\\?xml.*?>", String.Empty);
here xml is your xml data.

how do i deserialize this xml?

I need to deserialize the xml below. I want to use xmlserializer because I am (more) familiar with it.
I believe this xml is not constructed correctly however I cannot change it.
The below represents a list of category objects. When I try to deserialize using
xmlserializer(typeof(List<Category>))
I get this error: "categories xmlns='' is not expected"
<?xml version="1.0" encoding="utf-8" ?>
<categories>
<category id="16" name="Exports" parent_id="13"/>
<category id="17" name="Imports" parent_id="13"/>
<category id="3000" name="Income Payments & Receipts" parent_id="13"/>
<category id="125" name="Trade Balance" parent_id="13"/>
<category id="127" name="U.S. International Finance" parent_id="13"/>
</categories>
I don't mind making some kind of dummy class to deserilize these if that is what I have to do.
Here is my Category Class
[XmlType("category")]
public class Category
{
[XmlAttribute("id")]
public int ID { get; set; }
[XmlAttribute("parent_id")]
public int ParentID { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
}
My code:
XmlSerializer serializer = new XmlSerializer(typeof(List<Category>));
StringReader reader = new StringReader(xml);
List<Category> obj = null;
using (System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(reader))
{
obj = (List<Category>)serializer.Deserialize(xmlReader);
}
return obj;
You can just pass in the XmlRootAttribute into the serializer for the "categories" part.
BUT... you must remove the "&" from your xml because its not valid
XmlSerializer serializer = new XmlSerializer(typeof(List<Category>), new XmlRootAttribute("categories"));
using (FileStream fileStream = new FileStream(#"C:\Test.xml", FileMode.Open, FileAccess.Read, FileShare.Read))
{
var test = serializer.Deserialize(fileStream);
}
Here is your method working with a String.Replace to sort out the "&"
private List<Category> GetCategories(string xmlData)
{
List<Category> obj = null;
XmlSerializer serializer = new XmlSerializer(typeof(List<Category>), new XmlRootAttribute("categories"));
StringReader reader = new StringReader(xmlData.Replace("&","&"));
using (System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(reader))
{
obj = (List<Category>)serializer.Deserialize(xmlReader);
}
return obj;
}
Try to make a categories class that will contain your List<Category> like this:
[XmlRoot("categories")]
public class Categories
{
public Categories()
{
Items = new List<User>();
}
[XmlElement("category")]
public List<Category> Items {get;set;}
}
You can than create a serializer like this:
XmlSerializer serializer = new XmlSerializer(typeof(Categories));
Do you have an XSD that this XML should conform to? If so, you can generate the required code using:
"xsd your.xsd /classes"

Deserialize a class with interface

I have a class which contain an interface member variable.How can i deserialize this class
interface ISensor { }
[Serializable]
class Sensor: ISensor { }
[Serializable]
class Root
{
[XmlElement("Sensor")]
public List<ISensor> SensorList{ get; set; }
}
My XML will be like this
<?xml version="1.0" encoding="us-ascii"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Sensor >
<SensorName>Name1</SensorName>
<SensorValue>0.0</SensorValue>
</Sensor>
<Sensor>
<SensorName>Name2</SensorName>
<SensorValue>148.00</SensorValue>
</Sensor>
</Root>
Assuming you are using XmlSerializer, there are two changes required for both serialization and deserialization:
Tell the serializer the list of types that can appear, in other words the types that inherit from Sensor.
Use a class for the List rather than an interface, in other words replace List<ISensor> with List<Sensor>.
Unfortunately, the XmlSerializer does not handle interfaces in the way you want. See XmlSerializer serialize generic List of interface for more information.
If using a base class is not an option You could write your own XML serializer by implementing IXmlSerializable. Override ReadXml and parse the XML manually.
For example:
public interface ISensor { }
[Serializable]
public class Sensor : ISensor { }
[Serializable]
public class Root
{
// Changed List<ISensor> to List<Sensor>. I also changed
// XmlElement to XmlArray so it would appear around the list.
[XmlArray("Sensor")]
public List<Sensor> SensorList { get; set; }
}
[Serializable]
public class SensorA : Sensor
{
[XmlElement("A")]
public string A { get; set; }
}
[Serializable]
public class SensorB : Sensor
{
[XmlElement("B")]
public string B { get; set; }
}
class Program
{
public static void Main(string[] args)
{
XmlSerializer xmlSerializer;
Root root = new Root();
root.SensorList = new List<Sensor>();
root.SensorList.Add(new SensorA() {A = "foo"});
root.SensorList.Add(new SensorB() {B = "bar"});
// Tell the serializer about derived types
xmlSerializer = new XmlSerializer(typeof (Root),
new Type[]{typeof (SensorA), typeof(SensorB)});
StringBuilder stringBuilder = new StringBuilder();
using (StringWriter stringWriter = new StringWriter(stringBuilder))
{
xmlSerializer.Serialize(stringWriter, root);
}
// Output the serialized XML
Console.WriteLine(stringBuilder.ToString());
Root root2;
using (StringReader stringReader = new StringReader(stringBuilder.ToString()))
{
root2 = (Root) xmlSerializer.Deserialize(stringReader);
}
}
}
The output from the Console.WriteLine statement is:
<?xml version="1.0" encoding="utf-16"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Sensor>
<Sensor xsi:type="SensorA">
<A>foo</A>
</Sensor>
<Sensor xsi:type="SensorB">
<B>bar</B>
</Sensor>
</Sensor>
</Root>

Categories