XML Deserialization in List - c#

Ok i have an Xml file like this :
<?xml version="1.0" encoding="utf-8" ?>
<Books>
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
</Books>
And i want to use Deserialization in order to fill a List with books.
I have a class like this :
[Serializable]
public class Book
{
public string Name{get; set;}
public string Url { get; set; }
public string Genre { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
And a List like this :
List<Book> BookList;
I try to read from the xml File like this :
XmlSerializer serializer = new XmlSerializer(typeof(Book));
FileStream fs = new FileStream(#"Book.xml", FileMode.Open);
using (XmlReader reader = XmlReader.Create(fs))
{
BookList.Add((Book)serializer.Deserialize(reader));
reader.Close();
}
But i get an error in Xml File whenever i run my app. If i change my xml file to this :
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
everything goes fine , expect that i only read 1 book. What i want to do is read around 100 books. Any ideas?

if you change your deserialization code a little bit, it will work.
(See the usage of XmlRootAttribute & and type Book[])
XmlSerializer serializer = new XmlSerializer(typeof(Book[]),new XmlRootAttribute("Books"));
FileStream fs = new FileStream(#"Book.xml", FileMode.Open);
using (XmlReader reader = XmlReader.Create(fs))
{
var books = (Book[])serializer.Deserialize(reader);
}
BTW: you don't need Serializable attribute, It is only used by BinaryFormatter
PS: You can also use the type List<Book> instead of Book[]
EDIT
After modifiying your code a little bit more, It can be as simple as:
XmlSerializer serializer = new XmlSerializer(typeof(List<Book>),
new XmlRootAttribute("Books"));
using (FileStream fs = new FileStream(#"Book.xml", FileMode.Open))
{
List<Book> books = (List<Book>)serializer.Deserialize(fs);
}

You need to create a class that will contains a list of books and seriazlie this class:
[Serializable]
public class BookData
{
[XmlArray(ElementName="Books")]
[XmlArrayItem(ElementName="Book")]
public List<Book> Books {get; set;}
}
and then create the serializer like this:
var serializer = new XmlSerializer(typeof(BookData));

Instead of loading from FileStream that we can read from XmlDataDocument and iteration of the book element will give us a serialization pretty much easier. Please take a look at the modified approach.
List<Book> BookList = new List<Book>();
XmlDataDocument objDocument = new XmlDataDocument();
objDocument.Load(#"Book.xml");
XmlSerializer serializer = new XmlSerializer(typeof(Book));
foreach(XmlNode objItem in objDocument.DocumentElement.ChildNodes)
{
TextReader objTextReader = new StringReader(objItem.OuterXml);
using (XmlReader reader = XmlReader.Create(objTextReader))
{
BookList.Add((Book)serializer.Deserialize(reader));
reader.Close();
}
}

Related

How to remove empty namespace attribute on manually added xml string when serializing object?

I am using XmlSerializer to output my object model to XML. Everything works very well but now I need to add several lines of pre-built XML to the object without building classes for each line. After lots of searching, I found that I can convert the xml string to an XmlElement using XmlDocument's LoadXml and DocumentElement calls. I get the XML I want except that the string section has an empty namespace. How can I eliminate the empty namespace attribute? Is there a better way to add an xml string to the object and have it be serialized properly?
Note: I am only creating output so I don't need to deserialize the generated XML. I am fairly new to the C#, .NET world, and hence, XmlSerialize.
Here is my code:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public XmlElement Extension { get; set; }
public Book()
{
}
public void AddExtension()
{
string xmlString = "<AdditionalInfo>" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";
this.Extension = GetElement(xmlString);
}
public static XmlElement GetElement(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.DocumentElement;
}
}
static void Main(string[] args)
{
TestSerialization p = new TestSerialization();
Book bookOne = new Book();
bookOne.Title = "How to Fix Code";
bookOne.Author = "Dee Bugger";
bookOne.AddExtension();
System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(Book), "http://www.somenamespace.com");
using (var writer = new StreamWriter("C:\\BookReport.xml"))
using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = true }))
{
serializer.Serialize(xmlWriter, bookOne);
}
}
Here is my output:
<?xml version="1.0" encoding="utf-8"?>
<Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.somenamespace.com">
<Title>How to Fix Code</Title>
<Author>Dee Bugger</Author>
<Extension>
<AdditionalInfo xmlns="">
<SpecialHandling>Some Value</SpecialHandling>
</AdditionalInfo>
</Extension>
</Book>
It is the xmlns="" on AdditionalInfo that I want to eliminate. I believe this coming out because there is no association between the XmlDocument I created and the root serialized object, so the XmlDocument creates its own namespace. How can I tell the XmlDocument (and really, the generated XmlElement) that it belongs to the same namespace as the serialized object?
This is added because the parent elements have a namespace and your AdditionalInfo element does not. The xmlns="" attribute changes the default namespace for that element and its children.
If you want to get rid of it, then presumably you want the AdditionalInfo element to have the same namespace as its parent. In which case, you need to change your XML to this:
string xmlString = #"<AdditionalInfo xmlns=\"http://www.somenamespace.com\">" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";

Deserialization of XML

Trying to deserialize a XML log file. And cannot seem to get anything but Error in XML Document(0,0). I am guessing it has something to do with my class but I cannot seem to find a solution. I cannot change the XML formatting as this is a log file coming from a server (just simplified)
XML
<?xml version="1.0" encoding="utf-8"?>
<POSLog>
<Transaction>
<RetailStoreID>1</RetailStoreID>
<SequenceNumber>2</SequenceNumber>
</Transaction>
<Transaction>
<RetailStoreID>1</RetailStoreID>
<SequenceNumber>3</SequenceNumber>
</Transaction>
</POSLog>
Class
[Serializable()]
public class Transaction
{
[XmlElement("RetailStoreID")]
public string RetailStoreID { get; set; }
[XmlElement("SequenceNumber")]
public string SequenceNumber { get; set; }
}
[Serializable()]
[XmlRoot("POSLog")]
public class POSLog
{
[XmlArray("POSLog")]
[XmlArrayItem("Transaction", typeof(Transaction))]
public Transaction[] Transaction { get; set; }
}
Deserialize code
POSLog poslog = new POSLog();
string path = "POSLog.xml";
XmlSerializer serializer = new XmlSerializer(typeof(POSLog));
StreamReader reader = new StreamReader(path);
poslog = (POSLog)serializer.Deserialize(reader);
Found fix by switching from StreamReader to FileStream along with changes to the class. Changing encoding didn't seem to help when using the StreamReader.
I cannot reproduce the problem you are seeing. However, there is an issue with the POSLog class -- it needs to be defined as follows:
[Serializable()]
[XmlRoot("POSLog")]
public class POSLog
{
[XmlElement("Transaction")]
public Transaction[] Transaction { get; set; }
}
Your XML has a root element <POSLog> containing a repeating sequence of <Transaction> elements. [XmlElement("Transaction")] maps the array to just such a one-level repeating sequence.
Example fiddle.
Changing the Class to the follow above answer
[Serializable()]
[XmlRoot("POSLog")]
public class POSLog
{
[XmlElement("Transaction")]
public Transaction[] Transaction { get; set; }
}
Along with Changing the following StreamReader lines
StreamReader reader = new StreamReader(path);
poslog = (POSLog)serializer.Deserialize(reader);
To this:
FileStream fs = new FileStream(path, FileMode.Open);
poslog = (POSLog)serializer.Deserialize(fs);
Fixed the issue I was having with the root element and I was able to deserialize the XML. Thanks to dbc for help in getting me started solving a solution for the first time in a forum!

Reading large XML files with C#

I would like to know how can I read a XML file from my desktop and put it into a string?
Here is my XML:
<smallusers>
<user id="1">
<name>John</name>
<motto>I am john, who are you?</motto>
</user>
<user id="2">
<name>Peter</name>
<motto>Hello everyone!</motto>
</user>
</smallusers>
<bigusers>
<user id="3">
<name>Barry</name>
<motto>Earth is awesome</motto>
</user>
</bigusers>
I want to store each user, but still detect if their small or big, is there a way to do this?
Before you downrate this, you might want to check google because I did research, but found nothing.
"Before you downrate this, you might want to check google because I
did research, but found nothing"
You found nothing because you don't know what you are searching for, also your XML is invalid, you need to enclose it in a rootElement. Then the first thing you need to do is read this file from the desktop if it exists.
You can check the size if you wish at that time and determine if this is "too large" even though it doesn't really matter. I highly doubt your XML file will be 5+ GB in size. If it is then you need an alternative, no single object in a .Net program may be over 2GB, the best you could do is 1,073,741,823 on a 64bit machine.
For very large XML files, anything above 1.0 GB, combine XmlReader and LINQ as stated by Jon Skeet here:
If your document is particularly huge, you can combine XmlReader and
LINQ to XML by creating an XElement from an XmlReader for each of your
"outer" elements in a streaming manner: this lets you do most of the
conversion work in LINQ to XML, but still only need a small portion of
the document in memory at any one time.
For small XML files, anything 1.0 GB or lower stick to the DOM as shown below.
With that said, what you need is to learn what Serialization and Deserialization mean.
Serialize convert an object instance to an XML document.
Deserialize convert an XML document into an object instance.
Instead of XML you can also use JSON, binary, etc.
In your case this is what can be done to Deserialize this XML document back into an Object in order for you to use in your code.
First fix up the XML and give it a Root.
<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
<smallusers>
<user id="1">
<name>John</name>
<motto>I am john, who are you?</motto>
</user>
<user id="2">
<name>Peter</name>
<motto>Hello everyone!</motto>
</user>
</smallusers>
<bigusers>
<user id="3">
<name>Barry</name>
<motto>Earth is awesome</motto>
</user>
</bigusers>
</DataRoot>
Then create the root class in C#, you may generate this directly in Visual Studio 2012+ by copying your XML and going to Edit - Paste Special, but I like to use: XML to C# Class Generator
Here is what your code would look like after you generate the C# Root Class for your XML, hope it helps you understand it better.
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
public class Program
{
[XmlRoot(ElementName = "user")]
public class User
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "motto")]
public string Motto { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
[XmlRoot(ElementName = "smallusers")]
public class Smallusers
{
[XmlElement(ElementName = "user")]
public List<User> User { get; set; }
}
[XmlRoot(ElementName = "bigusers")]
public class Bigusers
{
[XmlElement(ElementName = "user")]
public User User { get; set; }
}
[XmlRoot(ElementName = "DataRoot")]
public class DataRoot
{
[XmlElement(ElementName = "smallusers")]
public Smallusers Smallusers { get; set; }
[XmlElement(ElementName = "bigusers")]
public Bigusers Bigusers { get; set; }
}
static void Main(string[] args)
{
string testXMLData = #"<DataRoot><smallusers><user id=""1""><name>John</name><motto>I am john, who are you?</motto></user><user id=""2""><name>Peter</name><motto>Hello everyone!</motto></user></smallusers><bigusers><user id=""3""><name>Barry</name><motto>Earth is awesome</motto></user></bigusers></DataRoot>";
var fileXmlData = File.ReadAllText(#"C:\XMLFile.xml");
var deserializedObject = DeserializeFromXML(fileXmlData);
var serializedToXML = SerializeToXml(deserializedObject);
//I want to store each user, but still detect if their small or big, is there a way to do this?
foreach (var smallUser in deserializedObject.Smallusers.User)
{
//Iterating your collection of Small users?
//Do what you need here with `smalluser`.
var name = smallUser.Name; //Example...
}
Console.WriteLine(serializedToXML);
Console.ReadKey();
}
public static string SerializeToXml(DataRoot DataObject)
{
var xsSubmit = new XmlSerializer(typeof(DataRoot));
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
xsSubmit.Serialize(writer, DataObject);
var data = sw.ToString();
writer.Flush();
writer.Close();
sw.Flush();
sw.Close();
return data;
}
}
}
public static DataRoot DeserializeFromXML(string xml)
{
var xsExpirations = new XmlSerializer(typeof(DataRoot));
DataRoot rootDataObj = null;
using (TextReader reader = new StringReader(xml))
{
rootDataObj = (DataRoot)xsExpirations.Deserialize(reader);
reader.Close();
}
return rootDataObj;
}
}
}

Create XML Hierarchy in C# from Text Doc

I need to take in a text document from a server and create an XML hierarchy from it in order to use it in a C# program.
It will be an organizational hierarchy. Here is the same of the text file:
EmployeeID; Employee Name; SupervisorID
1; Bob; 3
2; Mark; 1
3; Jill; 0
4; Ann ; 1
Where the above relationships would be:
Bob's boss is Jill. Mark's boss is Bob, and Jill has no boss.
I want to create an XML file from that data to look something like this:
<Employee> Jill
<Employee> Bob
<Employee> Mark </Employee>
<Employee> Ann </Employee>
</Employee>
</Employee>
I don't know if this makes sense, because I have never worked with C# or XML before.
Feel free to change how the tags are named, the main things I will need to do:
Be able to get the names of everyone supervised by the same person. (Ex: Mark would want Mark and Ann)
Be able to get the names of all supervisors above an employee (Ex: Mark's would be Bob and Jill)
Be able to get the names of all people under then (Ex: Mark would have nobody, Jill would have everybody, Bob would have Mark and Ann)
I've looked at XElement and XDoc and various tutorials and SO questions but most of the SO questions are too advanced for me at this point, and most of the tutorials aren't trying to create a hierarchy like mine.
Define a class Employee:
public class Employee{
[XmlIgnore]
public int ID{get;set;}
[XmlIgnore]
public int BossID{get;set;}
[XmlText]
public string Name{get;set;}
[XmlElement("Employee")
public Employee[] Minions{get;set;}
public SetMinions(List<Employee> list){
Minions = list.Where(e=>e.BossID==ID).ToArray();
}
}
then parse your input:
List<Employee> list = File.ReadAllLines("MyInputFile.txt")
.Select(l=>new Emplyee(){
ID = l.Split(';')[0],
Name= l.Split(';')[1],
BossID = l.Split(';')[2]}).ToList();
and then set the minions for each:
list.ForEach(e=>e.SetMinions(list));
And produce the XML like this:
XmlSerializer xser = new XmlSerializer(typeof(Employee));
xser.Serialize(File.OpenWrite("output.txt", list.First(e=>e.BossID==0)));
If it's not obvious this code is quite dirty and unreliable, add some checks and cleanups
Employee class:
public class Employee
{
public Employee()
{
Subordinates = new List<Employee>();
}
public int Id { get; set; }
public string Name { get; set; }
public int SupervisorId { get; set; }
public List<Employee> Subordinates { get; private set; }
public XElement ToXml()
{
return new XElement("Employee",
new XElement("Id", Id),
new XElement("Name", Name),
new XElement("Subordinates", Subordinates.Select(s => s.ToXml())));
}
}
And parsing logic:
// dictionary as a storage for data read from file
var Employees = new Dictionary<int,Employee>();
// file reading
var file = File.Open("Input.txt", FileMode.Open);
using (var reader = new StreamReader(file))
{
reader.ReadLine();
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
string[] fields = line.Split(';');
var newEmployee = new Employee { Id = int.Parse(fields[0]), Name = fields[1].Trim(), SupervisorId = int.Parse(fields[2]) };
Employees.Add(newEmployee.Id, newEmployee);
}
}
// filling Subordinates within every employee
foreach (var emp in Employees.Values)
{
if (Employees.ContainsKey(emp.SupervisorId))
Employees[emp.SupervisorId].Subordinates.Add(emp);
}
// taking first (root) employee by selecting the one with supervisorId == 0
var first = Employees.Values.First(e => e.SupervisorId == 0);
// XML generation
var xml = first.ToXml();
Result that I received from that code with your sample input:
<Employee>
<Id>3</Id>
<Name>Jill</Name>
<Subordinates>
<Employee>
<Id>1</Id>
<Name>Bob</Name>
<Subordinates>
<Employee>
<Id>2</Id>
<Name>Mark</Name>
<Subordinates />
</Employee>
<Employee>
<Id>4</Id>
<Name>Ann</Name>
<Subordinates />
</Employee>
</Subordinates>
</Employee>
</Subordinates>
</Employee>
I think that structure is better than you suggested. However, you can easily modify XML structure by modifying ToXml method within Employee class. Following one would give output you suggested:
public XElement ToXml()
{
return new XElement("Employee",
new XText(Name),
Subordinates.Select(s => s.ToXml()));
}
Result:
<Employee>Jill
<Employee>Bob
<Employee>Mark</Employee>
<Employee>Ann</Employee>
</Employee>
</Employee>

Using the .NET serializer to serialize XML to a .NET class

I have an XML file:
<?xml version="1.0" encoding="UTF-8"?>
<MyProducts>
<Product Name="P1" />
<Product Name="P2" />
</MyProducts>
And a C# object:
Public Class Product
{
[XmlAttribute]
Public Name {get; set;}
}
Using the .NET Serializer class now can I Deserialize the XML file into an IList without creating a MyProducts object?
So I want to somehow select only the Product elements before I serialize
There's a quick-and-dirty way to accomplish what you want - simply replace "MyProducts" with something the BCL classes are happy with - ArrayOfProduct:
string xml = #"<?xml version='1.0' encoding='UTF-8;'?>
<MyProducts>
<Product Name='P1' />
<Product Name='P2' />
</MyProducts>";
//secret sauce:
xml = xml.Replace("MyProducts>", "ArrayOfProduct>");
IList myProducts;
using (StringReader sr = new StringReader(xml))
{
XmlSerializer xs = new XmlSerializer(typeof(List<Product>));
myProducts = xs.Deserialize(sr) as IList;
}
foreach (Product myProduct in myProducts)
{
Console.Write(myProduct.Name);
}
Of course, the right way would be to transform the XML document to replace the MyProducts nodes appropriately - instead of using a string replace - but this illustrates the point.
If you don't want to create a collection class for your products, you can mix some LINQ to XML with the XmlSerializer:
public static IEnumerable<T> DeserializeMany<T>(
string fileName, string descendentNodeName = null) {
descendentNodeName = descendentNodeName ?? typeof(T).Name;
var serializer = new XmlSerializer(typeof(T));
return
from item in XDocument.Load(fileName).Descendants(descendentNodeName)
select (T)serializer.Deserialize(item.CreateReader());
}
Then, to get your list:
var products = XmlSerializerUtils.DeserializeMany<Product>(fileName).ToList();
I'm not sure you're going to have much success using the XML serializer to accomplish what you need. It may be simpler for you to manually parse out the XML and map them explicitly, e.g.
XDocument xml = XDocument.Parse(#"<?xml version=""1.0"" encoding=""UTF-8""?>
<MyProducts>
<Product Name=""P1"" />
<Product Name=""P2"" />
</MyProducts>");
foreach(var product in xml.Descendants(XName.Get("Product")))
{
var p = new Product { Name = product.Attribute(XName.Get("Name")).Value };
// Manipulate your result and add to your collection.
}
...
public class Product
{
public string Name { get; set; }
}
If you're using a file, which you most likely are for your XML, just replace the Parse method on the XDocument w/ Load and the appropriate signature.
I don't think you can instruct the serializer to spit out a IList. The serializer can create a MyProduct collection object and fill it with Products. Which sounds like what you want to do.
You can also use LINQ to query the XML document and create a list of IEnumerable as well.
// load from stream if that is the case
// this just uses a file for demonstration purposes
XDocument doc = XDocument.Load("location_of_source.xml");
// select all Product nodes from the root node and create a new Product object using
// object initialization syntax
var listOfProduct = doc.Descendants("Product")
.Select(p => new Product { Name = p.Attribute("Name").Value});
While it's novel to not create the class, doing so saves you a lot of code... You don't even have to use it except when you deserialize.
//Products declaration
[XmlRoot(ElementName = "MyProducts")]
public class MyProducts : List<Product>
{
}
public class Product
{
[XmlAttribute]
public string Name { get; set; }
}
...
[Test]
public void DeserializeFromString()
{
var xml = #"<?xml version='1.0' encoding='UTF-8;'?>
<MyProducts>
<Product Name='P1' />
<Product Name='P2' />
</MyProducts>";
IList<Product> obj = Serialization<MyProducts>.DeserializeFromString(xml);
Assert.IsNotNull(obj);
Assert.AreEqual(2, obj.Count);
}
...
//Deserialization library
public static T DeserializeFromString(string serialized)
{
var byteArray = Encoding.ASCII.GetBytes(serialized);
var memStream = new MemoryStream(byteArray);
return Deserialize(memStream);
}
public static T Deserialize(Stream stream)
{
var xs = new XmlSerializer(typeof(T));
return (T) xs.Deserialize(stream);
}
The problem is that IList is not serializable so you'd have to implement your custom XmlSerializer - walk through xml nodes etc, you can however deserialize your collection into List using out of the box XmlSerializer.
Dont forget to add default constructor to your Product Class.
XmlSerializer serializer = new XmlSerializer(typeof(List<Product>));
FileStream stream = new FileStream(fileName, FileMode.Open);
var product = serializer.Deserialize(sream) as List<Product>;

Categories