XML de-serialization using xml element/attributes - c#

Need your help in setting the xml attributes for XML deserialization.
This is my input xml:
<form>
<question id="QnA">
<answer>AnswerforA</answer>
</question>
<question id="QnB">
<answer>AnswerforB</answer>
</question>
<question id="QnC">
<answer>AnswerforC</answer>
</question>
</form>
The ids of each question element tag correspond to a class property and its value is the innertext of the corresponding answer element.
The .cs file will look like
public class Test
{
private string qnaAns;
private string qnbAns;
private string qncAns;
public string QnA
{
get{ return qnaAns;}
set{qnaAns = value;}
}
public string QnB
{
get{ return qnbAns;}
set{qnbAns = value;}
}
public string QnC
{
get{ return qncAns;}
set{qncAns = value;}
}
}
and I use the follwing code for deserialization
XmlSerializer ser = new XmlSerializer(typeof(Test));
XmlReader xr = new xmlReader(inputxml);
Test t = ser.Deserialize(xr) as Test;
Please let me know how to set the XML element/attribute for the Test class to achieve this.
Thanks for your time.

[XmlRoot("form")]
public class Form
{
[XmlElement("question")]
public List<Question> Questions { get; set; }
public Form()
{
Questions = new List<Question>();
}
}
public struct Question
{
[XmlAttribute("id")]
public string ID { get; set; }
[XmlElement("answer")]
public string Answer { get; set; }
}
Then to serialize, I use the following extensions:
public static bool XmlSerialize<T>(this T item, string fileName)
{
return item.XmlSerialize(fileName, true);
}
public static bool XmlSerialize<T>(this T item, string fileName, bool removeNamespaces)
{
object locker = new object();
XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
lock (locker)
{
using (XmlWriter writer = XmlWriter.Create(fileName, settings))
{
if (removeNamespaces)
{
xmlSerializer.Serialize(writer, item, xmlns);
}
else { xmlSerializer.Serialize(writer, item); }
writer.Close();
}
}
return true;
}
public static T XmlDeserialize<T>(this string s)
{
object locker = new object();
StringReader stringReader = new StringReader(s);
XmlTextReader reader = new XmlTextReader(stringReader);
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
lock (locker)
{
T item = (T)xmlSerializer.Deserialize(reader);
reader.Close();
return item;
}
}
finally
{
if (reader != null)
{ reader.Close(); }
}
}
public static T XmlDeserialize<T>(this FileInfo fileInfo)
{
string xml = string.Empty;
using (FileStream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd().XmlDeserialize<T>();
}
}
}
Hope this helps.
PS - The extensions came from my library on codeproject: http://www.codeproject.com/KB/dotnet/MBGExtensionsLibrary.aspx

Related

Serialize and De-serialize XML with commented sections in C#

I was wondering that how i can keep the commented part in XML (After serialization).
Is there any way to do so?
Here is my problem,
I have XML file with lots of nodes. My .NET application will load the XML file and serialize into C# class. Then will change some nodes (by BL) in the class and de-serialize and save the file again.
After saving, the comments i kept on some nodes are disappeared.
Is there is any way to avoid resetting of XML comments using C# ?
Thanks in advance
Suppose an xml like this
<?xml version="1.0" encoding="utf-8"?>
<Test>
<!--Foo Description!-->
<Foo>FooText</Foo>
<!--Bar Description!-->
<Bar>BarText</Bar>
</Test>
var xml = GenericSerializator<Test>.LoadObjectFromFile("test.xml");
xml.Foo += "1";
xml.FooCommnet += "2";
xml.Bar += "3";
xml.BarCommnet += "4";
GenericSerializator<Test>.SaveObjectToFile(xml, "test2.xml");
<?xml version="1.0" encoding="utf-16"?>
<Test>
<!--Foo Description!2-->
<Foo>FooText1</Foo>
<!--Bar Description!4-->
<Bar>BarText3</Bar>
</Test>
we can do it using this code:
internal static class GenericSerializator<T> where T : class
{
public static T LoadObjectFromFile(string fileName)
{
using (var file = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
var xmlSerializer = new XmlSerializer(typeof(T));
return (T)xmlSerializer.Deserialize(file);
}
}
public static void SaveObjectToFile(object value, string fileName)
{
var xmlSerializer = new XmlSerializer(typeof(T));
using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
fileStream.Seek(0, SeekOrigin.End);
using (var streamWriter = new StreamWriter(fileStream, Encoding.Unicode))
{
xmlSerializer.Serialize(streamWriter, value);
}
}
}
}
public class Test : XmlSerializableWithComments
{
[XmlIgnore, Description]
public string FooCommnet { get; set; }
public string Foo { get; set; }
[XmlIgnore, Description]
public string BarCommnet { get; set; }
public string Bar { get; set; }
}
public class XmlSerializableWithComments : IXmlSerializable
{
private PropertyInfo[] Properties { get; set; }
public XmlSerializableWithComments()
{
Properties = GetType().GetProperties();
}
public void WriteXml(XmlWriter writer)
{
foreach (var propertyInfo in Properties)
{
var value = propertyInfo.GetValue(this, null).ToString();
if (propertyInfo.IsDefined(typeof(DescriptionAttribute), false))
{
writer.WriteComment(value);
}
else
{
writer.WriteElementString(propertyInfo.Name, value);
}
}
}
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.Read();
}
string comment = null;
if (reader.NodeType == XmlNodeType.Comment)
{
comment = reader.Value;
}
reader.Read();
if (reader.NodeType == XmlNodeType.Element)
{
var propertyName = reader.LocalName;
PropertyInfo temp;
if ((temp = Properties.FirstOrDefault(i => i.Name == propertyName)) != null)
{
reader.Read();
temp.SetValue(this, reader.Value);
if (!string.IsNullOrEmpty(comment))
{
if ((temp = Properties.FirstOrDefault(i => i.Name == propertyName + "Commnet")) != null)
{
temp.SetValue(this, comment);
}
comment = null;
}
}
}
}
}
}
}

Custom class with IXmlSerializable fails with OutOfMemoryException

I have the following xml file:
<MyConfig>
<Item a1="Attrib11" a2="Attrib21" a3="Attrib31" />
<Item a1="Attrib12" a2="Attrib22" a3="Attrib32" />
</MyConfig>
I load it in using the following helper methods:
public static T Load<T>(string path)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (StreamReader sr = new StreamReader(fs))
{
return (T)xml.Deserialize(sr);
}
}
public static void Save<T>(string path, T contents)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
using (FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
using (StreamWriter sw = new StreamWriter(fs))
{
xml.Serialize(sw, contents, ns);
}
}
This is MyConfig:
public class MyConfig
{
[XmlElement("Item")]
public List<Item> Items { get; set; }
public MyConfig()
{
Items = new List<Item>();
}
}
public class Item : IXmlSerializable
{
[XmlAttribute()]
public string Attrib1 { get; set; }
[XmlAttribute()]
public string Attrib2 { get; set; }
[XmlAttribute()]
public string Attrib3 { get; set; }
public Item(string attrib1, string attrib2, string attrib3)
{
Attrib1 = attrib1;
Attrib2 = attrib2;
Attrib3 = attrib3;
}
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element)
{
Attrib1 = reader.GetAttribute("a1");
Attrib2 = reader.GetAttribute("a2");
Attrib3 = reader.GetAttribute("a3");
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("a1", Attrib1);
writer.WriteAttributeString("a2", Attrib2);
writer.WriteAttributeString("a3", Attrib3);
}
}
I then have the following test bed code for checking the serialization of the class:
string file = "somePath";
MyConfig myConfig = new MyConfig()
{
Items = new List<Item>()
{
new Item("Attrib11", "Attrib21", "Attrib31"),
new Item("Attrib12", "Attrib22", "Attrib32"),
},
};
Save(file, myConfig);
MyConfig myConfig2 = Load<MyConfig>(file);
This fails with an OutOfMemory exception at Load, how can I fix this? Checking the file at somePath and it looks correct.
You need to tell the reader to advance to the next node after you've read the attributes:
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element)
{
Attrib1 = reader.GetAttribute("a1");
Attrib2 = reader.GetAttribute("a2");
Attrib3 = reader.GetAttribute("a3");
}
// Go to the next node.
reader.Read();
}
If you don't call reader.Read(), the reader reads the same node over and over again, and therefore the XmlSerializer will create an unlimited amount of Item instances until you finally get the OutOfMemoryException.

Error when deserializing

I'm trying to write some code to deserialize an XML file. I've looked around a bit and found something which has led me to the following method:
public static void Deserialize(string filePath)
{
RootObject ro = null;
string path = filePath;
XmlSerializer serializer = new XmlSerializer(typeof(RootObject));
StreamReader reader = new StreamReader(path);
ro = (RootObject) serializer.Deserialize(reader);
reader.Close();
}
But all I get is this error and I'm not sure what's causing it:
There is an error in XML document (2, 2).
The RootObject you see in Deserialize() is this one: I'm new to XMl serializing/deserializing so I'm not sure if I've defined it 100% correctly.
public class RootObject
{
public Services Services { get; set; }
}
public class Services
{
public Service TileMapService { get; set; }
}
public class Service
{
public string Title { get; set; }
public string href { get; set; }
}
I'm using this method to create the XML files in the first place and it seems to work fine:
public static void WriteToXmlFile<T>(string filePath, T objectToWrite) where T : new()
{
TextWriter writer = null;
try
{
var serializer = new XmlSerializer(typeof (T));
writer = new StreamWriter(filePath);
serializer.Serialize(writer, objectToWrite);
}
finally
{
if (writer != null)
{
writer.Close();
}
}
}
It gets me an XML file that looks like this:
<RootObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Services>
<TileMapService>
<Title>Some title</Title>
<href>http://something</href>
</TileMapService>
</Services>
</RootObject>
Your code works fine. The "There is an error in XML document (2, 2)." might be because your actual file (not the one created by WriteToXmlFile<T>) has whitespace at the start, or is using the wrong namespace. Check the .InnerException for more detail, or (perhaps simpler) please post the actual xml file contents. Example of it working fine (along with some recommended tweaks to the two key methods):
static void Main()
{
RootObject obj = new RootObject
{
Services = new Services
{
TileMapService = new Service
{
Title = "abc",
href = "def"
}
}
};
WriteToXmlFile("foo.xml", obj);
var loaded = Deserialize<RootObject>("foo.xml");
var svc = loaded.Services.TileMapService;
System.Console.WriteLine(svc.Title); // abc
System.Console.WriteLine(svc.href); // def
}
public static void WriteToXmlFile<T>(string filePath, T objectToWrite)
{
var serializer = new XmlSerializer(typeof(T));
using (var writer = new StreamWriter(filePath))
{
serializer.Serialize(writer, objectToWrite);
}
}
public static T Deserialize<T>(string filePath)
{
var serializer = new XmlSerializer(typeof(T));
using (var reader = new StreamReader(filePath))
{
return (T)serializer.Deserialize(reader);
}
}

PaymentDetails is not successfully deserialized, returning a null object, because it seems it is expected to have IXmlDeserializable

[XmlRoot("Quote")]
public class Quote
{
[XmlElement("Insurance")]
public InsuranceDetails InsDetails { get; set; }
[XmlElement("Payment")]
public PaymentDetails PayDetails { get; set; }
}
public class InsuranceDetails : IXmlSerializable
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("Insurance");
Details1 = reader.ReadElementString("Details1");
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
// do write suff
}
}
public class PaymentDetails
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
}
Given this example, using XmlSerializer to deserialize my string to QuoteObject, PaymentDetails is not successfully deserialized, returning a null object, because it seems it is expected to have IXmlDeserializable. It only works if PaymentDetails is parsed in first place. Is this some expected behavior from XmlSerializer?
using (TextReader read = new StringReader(xml))
{
var serializer = new XmlSerializer(typeof(Quote));
return (Quote)serializer.Deserialize(read);
}
Well these are the ReadXml and WriteXml I modified:
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
var empty=reader.IsEmptyElement;
reader.ReadStartElement();
if(!empty){
Details1=reader.ReadElementString("Details1");
reader.ReadEndElement();
}
}
public void WriteXml(XmlWriter writer)
{
var str=string.IsNullOrWhiteSpace(Details1)?"":Details1;
writer.WriteElementString("Details1",str);
}
Following are serialize and deserialize functions:
public static string Serialize<T>(T t)
{
var xmlser=new XmlSerializer(typeof(T));
XmlWriterSettings settings = new XmlWriterSettings();
using(StringWriter textWriter = new StringWriter()) {
using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
xmlser.Serialize(xmlWriter, t);
}
return textWriter.ToString();
}
}
public static T Deserialize<T>(string xml)
{
if(string.IsNullOrEmpty(xml)) {
return default(T);
}
XmlSerializer serializer = new XmlSerializer(typeof(T));
XmlReaderSettings settings = new XmlReaderSettings();
using(StringReader textReader = new StringReader(xml)) {
using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
return (T) serializer.Deserialize(xmlReader);
}
}
}
Serialization Test:
var q=new Quote();
q.PayDetails = new PaymentDetails{Details1="Payment Details 1"};
q.InsDetails=new InsuranceDetails{Details1="Insurance Details 1"};
str = Serialize<Quote>(q);
Which gives (str):
<?xml version="1.0" encoding="utf-16"?>
<Quote xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Payment>
<Details1>Payment Details 1</Details1>
</Payment>
<Insurance>
<Details1>Insurance Details 1</Details1>
</Insurance>
</Quote>
Deserialization Test:
var dq=Deserialize<Quote>(str);
Console.WriteLine(dq.PaymentDetails.Detail1);//gives "Payment Details 1"
Console.WriteLine(dq.InsuranceDetails.Detail1);//gives "Insurance Details 1"
PS:- The Serialize code was copied from another SO answer verbatim. I learned how to serialize to string using StringWriter.
First of all you don't have to implement IXmlSerializable in any of the classes. Second of all, you don't provide the content of the xml variable. It may contain a mistype/bug, if you created it manually.
I used the following code, to test your classes:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace XmlDeSerialize
{
[XmlRoot("Quote")]
public class Quote
{
[XmlElement("Insurance")]
public InsuranceDetails InsDetails { get; set; }
[XmlElement("Payment")]
public PaymentDetails PayDetails { get; set; }
}
public class InsuranceDetails
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
}
public class PaymentDetails
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
}
class Program
{
static void Main(string[] args)
{
var qin = new Quote
{
InsDetails = new InsuranceDetails { Details1 = "insurance details text" },
PayDetails = new PaymentDetails { Details1 = "payment details text" },
};
string xml;
using (var stream = new MemoryStream())
{
var serializer = new XmlSerializer(typeof(Quote));
serializer.Serialize(stream, qin);
stream.Position = 0;
using (var sr = new StreamReader(stream))
{
xml = sr.ReadToEnd();
}
}
Quote qout;
using (TextReader read = new StringReader(xml))
{
var deserializer = new XmlSerializer(typeof(Quote));
var obj = deserializer.Deserialize(read);
qout = (Quote)obj;
}
Console.WriteLine("InsDetails.Details1='{0}'", qout.InsDetails.Details1);
Console.WriteLine("PayDetails.Details1='{0}'", qout.PayDetails.Details1);
}
}
}
The value of xml after serialization:
<?xml version="1.0"?>
<Quote xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Insurance>
<Details1>insurance details text</Details1>
</Insurance>
<Payment>
<Details1>payment details text</Details1>
</Payment>
</Quote>
The console output I received:
InsDetails.Details1='insurance details text'
PayDetails.Details1='payment details text'
Try the code yourself and see if it works for you. Clearly to me you don't provide valid XML content for deserialization, or other part of your code you did not provide in your question is to blame.

serialize and store object in another object that implements IXmlSerializable

I would like to XML serialize instances of my object Exception and store it in the XMLNode[] Nodes property of another object ExceptionReport.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Xml.Serialization.XmlSchemaProviderAttribute("ExportSchema")]
[System.Xml.Serialization.XmlRootAttribute(IsNullable = false)]
public partial class ExceptionReport : object, System.Xml.Serialization.IXmlSerializable
{
public System.Xml.XmlNode[] Nodes { get; set; }
public void ReadXml(System.Xml.XmlReader reader)
{
this.Nodes = System.Runtime.Serialization.XmlSerializableServices.ReadNodes(reader);
}
public void WriteXml(System.Xml.XmlWriter writer)
{
System.Runtime.Serialization.XmlSerializableServices.WriteNodes(writer, this.Nodes);
}
}
public class Exception
{
public string ExceptionText;
public string exceptionCode;
public string locator;
}
How would i go about doing this so the result would be something like this:
<ExceptionReport xmlns="http://www.opengis.net/ows" >
<Exception exceptionCode="1">my first instance</Exception>
<Exception exceptionCode="2">my second instance</Exception>
</ExceptionReport>
So far i have the following but i need to know how to serialize these objects and store them in the ExceptionReport Nodes array.
ExceptionReport er = new ExceptionReport();
Exception exception_item1 = new Exception();
exception_item1.ExceptionText = "my first instance";
exception_item1.exceptionCode = "1";
Exception exception_item2 = new Exception();
exception_item2.ExceptionText = "my second instance";
exception_item2.exceptionCode = "2";
List<Exception> exceptions = new List<Exception>( exception_item1, exception_item2 );
[XmlRoot("ExceptionReport")]
public partial class ExceptionReport
{
[XmlElement("Exception")]
public List<Exception> Nodes { get; set; }
public ExceptionReport()
{
Nodes = new List<Exception>();
}
}
public class Exception
{
[XmlText]
public string ExceptionText;
[XmlAttribute("exceptionCode")]
public int ExceptionCode;
[XmlAttribute("locator")]
public string Locator;
}
Then to serialize, I use the following extensions:
public static bool XmlSerialize<T>(this T item, string fileName)
{
return item.XmlSerialize(fileName, true);
}
public static bool XmlSerialize<T>(this T item, string fileName, bool removeNamespaces)
{
object locker = new object();
XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
lock (locker)
{
using (XmlWriter writer = XmlWriter.Create(fileName, settings))
{
if (removeNamespaces)
{
xmlSerializer.Serialize(writer, item, xmlns);
}
else { xmlSerializer.Serialize(writer, item); }
writer.Close();
}
}
return true;
}
public static T XmlDeserialize<T>(this string s)
{
object locker = new object();
StringReader stringReader = new StringReader(s);
XmlTextReader reader = new XmlTextReader(stringReader);
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
lock (locker)
{
T item = (T)xmlSerializer.Deserialize(reader);
reader.Close();
return item;
}
}
finally
{
if (reader != null)
{ reader.Close(); }
}
}
public static T XmlDeserialize<T>(this FileInfo fileInfo)
{
string xml = string.Empty;
using (FileStream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd().XmlDeserialize<T>();
}
}
}
Use like this:
ExceptionReport report = new ExceptionReport();
report.Nodes.Add(new Exception { ExceptionText = "my first instance", ExceptionCode = 1, Locator = "loc1" });
report.Nodes.Add(new Exception { ExceptionText = "my second instance", ExceptionCode = 2 });
report.XmlSerialize("C:\\test.xml");
I tested and it came out like you wanted. Hope it helps...
PS - The extensions came from my library on codeproject: http://www.codeproject.com/KB/dotnet/MBGExtensionsLibrary.aspx

Categories