Correct way to convert Xml to Objects? - c#

I have very simple Xml structure that I want to convert to list of objects. My code does work but I think this is not the correct way of doing this and since I never did this I think there might be simpler way of doing what I want.
Xml example
<root>
<item>
<name>Item 1</name>
<price>30.00</price>
</item>
<item>
<name>Item 2</name>
<price>55.00</price>
</item>
</root>
Code to gather xml and create list of objects
class Program
{
static void Main(string[] args)
{
List<Item> itemList = new List<Item>();
var url = "http://xmlurl.com/xml";
// Load xml data
XmlDocument myXmlDocument = new XmlDocument();
myXmlDocument.Load(url);
// Select items and loop
var xmlItems = myXmlDocument.SelectNodes("/root/item");
foreach (XmlNode item in xmlItems)
{
var newItem = new Item();
foreach (XmlNode i in item)
{
// Since I cannot query them properly I need to check every item node
switch (i.Name)
{
case "name":
newItem.Name = i.InnerText;
break;
case "price":
newItem.Price = Convert.ToDecimal(i.InnerText);
break;
}
}
itemList.Add(newItem);
}
// Test it out
foreach (var item in itemList.OrderBy(x => x.Price))
{
Console.WriteLine(item.Name + " | " + item.Price);
}
Console.ReadLine();
}
}
class Item
{
public string Name { get; set; }
public decimal Price { get; set; }
}

Using LINQ:
XDocument xdoc = XDocument.Load("myXml.xml");
List<Item> items = (from item in xdoc.Descendants("item")
select new Item {
Name = item.Element("name").Value,
Price = item.Element("price").Value
}).ToList();

You should use XmlSerializer, example:
Classes:
[XmlType(TypeName="item")]
public class Item {
public string Name { get; set; }
public decimal Price { get; set; }
}
[XmlRoot(ElementName = "root")]
public class ItemList : List<Item> {
}
Getting them from markup:
const string test = #"<root>
<item>
<name>Item 1</name>
<price>30.00</price>
</item>
<item>
<name>Item 2</name>
<price>55.00</price>
</item>
</root>";
var serializer = new XmlSerializer(typeof(ItemList));
List<Item> result;
using (var reader = new StringReader(test)) {
result = (List<Item>)serializer.Deserialize(reader);
}

You can define your class:
[XmlType("item")]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("price")]
public decimal Price { get; set; }
}
And then deserialize the Xml:
var xml = #"<root>
<item>
<name>Item 1</name>
<price>30.00</price>
</item>
<item>
<name>Item 2</name>
<price>55.00</price>
</item>
</root>";
List<Item> items;
var serializer = new XmlSerializer(typeof(List<Item>),
new XmlRootAttribute("root"));
using(var stream = new StringReader(xml))
{
items = (List<Item>)serializer.Deserialize(stream);
}
if(items != null)
{
foreach(var item in items)
{
Console.Write(item);
}
}

Related

How to parse xml with LINQ to an Object with XDocument

I have an xml file as below:
<Message xsi:schemaLocation ="..">
<Header>..</Header>
<Body>
<History
xmlns="..">
<Number></Number>
<Name></Name>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description></Description>
</Item>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description>1..</Description>
<Description>2..</Description>
</Item>
</History>
</Body>
</Message>
I would like to create and object from this as History object.
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
var xElement = XDocument.Parse(xmlString);
XElement body = (XElement)xElement.Root.LastNode;
XElement historyElement = (XElement)body.LastNode;
var history = new History
{
Name = (string)historyElement.Element("Name"),
Number = (string)historyElement.Element("Number"),
Items = (
from e in historyElement.Elements("Item")
select new Item
{
CraeteDate = DateTime.Parse(e.Element("CreateDate").Value),
Type = (string)e.Element("Type").Value,
Description = string.Join(",",
from p in e.Elements("Description") select (string)p.Element("Description"))
}).ToList()
};
Why this does not work?
The values are always null.
It seems that "historyElement.Element("Name")" is always null even there is an element and value for the element.
Any idea what am I missing?
Thanks
It's due to the namespace, try doing this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";// the namespace you have in the history element
var xElement = XDocument.Parse(xmlString);
var history= xElement.Descendants(ns+"History")
.Select(historyElement=>new History{ Name = (string)historyElement.Element(ns+"Name"),
Number = (string)historyElement.Element(ns+"Number"),
Items = (from e in historyElement.Elements(ns+"Item")
select new Item
{
CraeteDate= DateTime.Parse(e.Element(ns+"CreateDate").Value),
Type = (string) e.Element(ns+"Type").Value,
Description= string.Join(",",
from p in e.Elements(ns+"Description") select (string)p)
}).ToList()
}).FirstOrDefault();
If you want to read more about this subject, take a look this link
A couple of minor things here, the xml was malformed here. So had to take a while to test and make it work.
You have an xsi in the front which I assume should be somewhere mentioned in the xsd.
Turns out you have to append the namespace if your xml node has a namespace attached to it as you are parsing the xml tree here:
My sample solution looked like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication1
{
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public DateTime? CreateDate { get; set; }
public string Type { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xmlString =
#"<Message>
<Header>..</Header>
<Body>
<History
xmlns=""http://schemas.somewhere.com/types/history"">
<Number>12</Number>
<Name>History Name</Name>
<Item>
<CreateDate></CreateDate>
<Type>Item 1 Type</Type>
<Description>Item 1 Description</Description>
</Item>
<Item>
<CreateDate></CreateDate>
<Type>Item 2 Type</Type>
<Description>Item 2 Description 1</Description>
<Description>Item 2 Description 2</Description>
</Item>
</History>
</Body>
</Message>";
XNamespace ns = "http://schemas.somewhere.com/types/history";
var xElement = XDocument.Parse(xmlString);
var historyObject = xElement.Descendants(ns +"History")
.Select(historyElement => new History
{
Name = historyElement.Element(ns + "Name")?.Value,
Number = historyElement.Element(ns + "Number")?.Value,
Items = historyElement.Elements(ns + "Item").Select(x => new Item()
{
CreateDate = DateTime.Parse(x.Element(ns + "CreateDate")?.Value),
Type = x.Element(ns + "Type")?.Value,
Description = string.Join(",", x.Elements(ns + "Description").Select(elem=>elem.Value))
}).ToList()
}).FirstOrDefault();
}
}
}
If you dont wan't to care about finding the namespace, you might want to try the following:
var document = XDocument.Parse(xmlString);
var historyObject2 = document.Root.Descendants()
.Where(x=>x.Name.LocalName == "History")
.Select(historyElement => new History
{
Name = historyElement.Element(historyElement.Name.Namespace + "Name")?.Value,
Number = historyElement.Element(historyElement.Name.Namespace+ "Number")?.Value,
Items = historyElement.Elements(historyElement.Name.Namespace + "Item").Select(x => new Item()
{
//CreateDate = DateTime.Parse(x.Element("CreateDate")?.Value),
Type = x.Element(historyElement.Name.Namespace + "Type")?.Value,
Description = string.Join(",", x.Elements(historyElement.Name.Namespace + "Description").Select(elem => elem.Value))
}).ToList()
}).FirstOrDefault();

Serialize object to specific format, remove namespaces and move attribute

I'm trying to build serialization for my application that must export data in specific format, below is sample what is expected:
<?xml version="1.0" encoding="utf-8"?>
<sync>
<table name="Test" diff="0" mode="db">
<keys>
<key>MY_NUMBER</key>
<key>ID</key>
</keys>
<items task="modify">
<item ID="OK" MY_NUMBER="two"/>
<item ID="NT" MY_NUMBER="two"/>
</items>
</table>
<table name="Second" diff="1" mode="x">
<keys>
<key>ID</key>
</keys>
<items task="add">
<item ID="x" TYPE="c"/>
</items>
</table>
</sync>
I was able to get similar results:
<?xml version="1.0" encoding="utf-8"?>
<sync>
<table name="Test" diff="0" mode="db" task="modify">
<keys>
<key>MY_NUMBER</key>
<key>ID</key>
</keys>
<items>
<item d4p1:type="FirstItem" ID="OK" MY_NUMBER="two" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<item d4p1:type="FirstItem" ID="NT" MY_NUMBER="two" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
</items>
</table>
<table name="SecondTest" diff="1" mode="x" task="add">
<keys>
<key>ID</key>
</keys>
<items>
<item d4p1:type="SecondItem" ID="x" TYPE="c" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
</items>
</table>
</sync>
But I get unwanted namespaces, I've tried searching SO for solutions (Omitting all xsi and xsd namespaces when serializing an object in .NET?, Remove Namespaces During XML Serialization) but without luck.
I have my classes defined like this:
namespace Sync.Models
{
[XmlRoot("sync")]
[XmlInclude(typeof(FirstItem))]
[XmlInclude(typeof(SecondItem))]
public class Export
{
[XmlElement(ElementName = "table")]
public Table users { get; set; }
[XmlElement(ElementName = "table2")]
public Table items { get; set; }
}
public class Table
{
[XmlAttribute("name")]
public string name { get; set; }
[XmlAttribute("diff")]
public int diff { get; set; }
[XmlAttribute("mode")]
public string mode { get; set; }
[XmlArray("keys")]
[XmlArrayItem("key")]
public List<string> Keys { get; set; }
[XmlArray("items")]
[XmlArrayItem("item")]
public List<BaseItem> Items { get; set; }
[XmlAttribute("task")]
public string Task { get; set; }
}
public class FirstItem:BaseItem
{
[XmlAttribute("MY_NUMBER")]
public string Number { get; set; }
}
public class SecondItem:BaseItem
{
[XmlAttribute("TYPE")]
public string Type { get; set; }
}
}
And finally my serialization functionality:
var testData = new Export
{
users = new Table
{
name = "Test",
diff = 0,
mode = "db",
Keys = new List<string> { "MY_NUMBER", "ID" },
Items = new List<BaseItem>
{
new FirstItem {Id = "OK", Number = "two"},
new FirstItem {Id = "NT", Number = "two"}
},
Task = "modify"
},
items = new Table
{
name = "SecondTest",
diff = 1,
mode = "x",
Keys = new List<string> { "ID" },
Items = new List<BaseItem>
{
new SecondItem{Id = "x",Type = "c"}
},
Task = "add"
}
};
var fileName_tmp = String.Format(#"{0}\xml1.xml", Application.StartupPath);
var fileName = String.Format(#"{0}\xml.xml", Application.StartupPath);
var serializer = new XmlSerializer(typeof(Export));
using (TextWriter writer = new StreamWriter(fileName_tmp))
{
serializer.Serialize(writer, testData, new XmlSerializerNamespaces(new[] {XmlQualifiedName.Empty}));
}
using (FileStream inputStream = File.OpenRead(fileName_tmp))
{
using (StreamReader inputReader = new StreamReader(inputStream))
{
using (StreamWriter outputWriter = File.CreateText(fileName))
{
string tempLineValue;
while (null != (tempLineValue = inputReader.ReadLine()))
{
outputWriter.WriteLine(tempLineValue.Replace("table2", "table"));
}
}
}
}
1. I would like to remove unwanted namespaces and type attribute from nodes, but as I wrote before solution I found isn't working.
2. I need to have multiple nodes with same name (table), for now my only solution is to replace tag after serialization. I know I could use List to store tables, but during serialization this is giving me one extra unwanted level-can this be removed? Or custom serialization is only option?
3. Right now Task property from table is stored as attribute of table node in xml. Can I move it to items, to get desired result? Or do I must create custom serialization?
you can remove namesapces by add this code
using (FileStream stream = new FileStream("FilePath",FileMode.Create))
{
XmlSerializer serializer = new XmlSerializer(typeof(YourClass));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(stream," Your Object to Serialize",ns);
}

I need to get the childnodes of a parent in a list in XML so that I can put them in a loop to put all values in Excel

I am sending one XML string as a webservices, and I want to interpret it. My XML string is like this:
<?xml version="1.0" encoding="utf-8"?>
<result is_array="true">
<item>
<candidate_offer_id>175</candidate_offer_id><contact_person>Ranjeet Singh</contact_person><offer_status>8</offer_status>
</item>
<item><candidate_offer_id>176</candidate_offer_id><contact_person>Ranjeet Singh</contact_person><offer_status>8</offer_status>
</item>
</result>
In this XML string I want to access the child node like candidate_offer_id, offer_status name under node <item> in a list so that later I can run a loop to get all these values in a loop and put it on the Excel sheet. Until now I have written like this:
WebResponse response = request.GetResponse();
Stream responseStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(responseStream);
string responseFromServer = reader.ReadToEnd();
StringBuilder output = new StringBuilder();
var str = XElement.Parse(responseFromServer);
var result = str.Element("item");
But how do I interpert the var result to get the tag name of parent node <item> in a list and also how to interpret it?
You can also use XmlDocument class to load and manipulate the XML.
XmlDocument doc = new XmlDocument();
//doc.Load(responseStream); -- Stream loading
doc.LoadXml(responseFromServer); // where responseFromServer is a xml string
XmlNodeList list = doc.DocumentElement.SelectNodes("//item/*");
foreach (XmlNode n in list)
{
Console.WriteLine("{0} : {1}", n.Name, n.Value);
}
// As a list object that can be converted further
IEnumerable<XmlNode> node = list.Cast<XmlNode>();
I am presuming that the <code> tags are in error; if not, the xml will need correcting before if can be used.
* Update : Simplified the code to extract child nodes as list
First, your XML is invalid. You want <code> after <?xml> like:
<?xml version=""1.0"" encoding=""utf-8""?>
<code>
<result is_array=""true"">
<item>
<candidate_offer_id>175</candidate_offer_id><contact_person>Ranjeet Singh</contact_person><offer_status>8</offer_status>
</item>
<item><candidate_offer_id>176</candidate_offer_id><contact_person>Ranjeet Singh</contact_person><offer_status>8</offer_status>
</item>
</result>
</code>
I'd make a class to contain each item like
static void Main(string[] args)
{
var str = XElement.Parse(xml);
var items = str.Descendants("item");
List<Item> Items = new List<Item>();
foreach (var item in items)
{
Items.Add(new Item
{
OfferID = Convert.ToInt32(item.Element("candidate_offer_id").Value),
Person = item.Element("contact_person").Value,
Status = Convert.ToInt32(item.Element("offer_status").Value)
});
}
}
class Item
{
public int OfferID { get; set; }
public string Person { get; set; }
public int Status { get; set; }
}
XDocument will work for this case since you're not using the entire XML structure.
Assuming your XML is valid such as:
<?xml version="1.0" encoding="UTF-8"?>
<result is_array="true">
<item>
<candidate_offer_id>175</candidate_offer_id>
<contact_person>Ranjeet Singh</contact_person>
<offer_status>8</offer_status>
</item>
<item>
<candidate_offer_id>176</candidate_offer_id>
<contact_person>Ranjeet Singh</contact_person>
<offer_status>8</offer_status>
</item>
</result>
DTO:
public class CandidateOffer
{
public int CandidateOfferId { get; set; }
public string ContactPerson { get; set; }
public int OfferStatus { get; set; }
}
Parser:
public CandidateOffer ParseCandidateOffer(XElement element)
{
int candidateOfferId;
if(!int.TryParse(element.Element("candidate_offer_id").Value,
out candidateOfferId))
{
candidateOfferId = 0;
}
var contactPerson = element.Element("contact_person").Value;
int offerStatus;
if(!int.TryParse(element.Element("offer_status").Value,
out offerStatus))
{
offerStatus = 0;
}
return new CandidateOffer
{
CandidateOfferId = candidateOfferId,
ContactPerson = contactPerson,
OfferStatus = offerStatus
};
}
Usage:
var xDocument = XDocument.Parse(xmlString);
var candidateOffers = xDocument.XPathSelectElements("//item")
.Select(ParseCandidateOffer);
foreach(var candidateOffer in candidateOffers)
{
Console.WriteLine(candidateOffer.CandidateOfferId);
}

XMLSerialization for a List

I have class like this below shown. which contains the shopping items where the number can vary from 0 to n.
namespace SerializationPOC
{
public class ShoppingItems
{
public string CustomerName { get; set; }
public string Address { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public string Name { get; set; }
public string Price { get; set; }
}
}
Is it possible to get the class serialized like to get the XML Schema like below.
<?xml version="1.0" encoding="utf-8" ?>
<ShoppingItems>
<CustomerName>John</CustomerName>
<Address>Walstreet,Newyork</Address>
<Item1>Milk</Item1>
<Price1>1$</Price1>
<Item2>IceCream</Item2>
<Price2>1$</Price2>
<Item3>Bread</Item3>
<Price3>1$</Price3>
<Item4>Egg</Item4>
<Price4>1$</Price4>
<Item..n>Egg</Item..n>
<Price..n>1$</Price..n>
</ShoppingItems>
I would like to know if this can be achieved by using the Serilization if not whats the best way to achieve this Schema?
There is no standard serializer that supports that layout. You will have to do it yourself. Personally, I would say "you're doing it wrong"; I strongly suggest (if it is possible) using a format like
<Item name="IceCream" Price="1$"/>
or
<Item><Name>IceCream</Name><Price>1$</Price></Item>
both of which would be trivial with XmlSerializer.
LINQ-to-XML is probably your best option, something like:
var items = new ShoppingItems
{
Address = "Walstreet,Newyork",
CustomerName = "John",
Items = new List<Item>
{
new Item { Name = "Milk", Price = "1$"},
new Item { Name = "IceCream", Price = "1$"},
new Item { Name = "Bread", Price = "1$"},
new Item { Name = "Egg", Price = "1$"}
}
};
var xml = new XElement("ShoppingItems",
new XElement("CustomerName", items.CustomerName),
new XElement("Address", items.Address),
items.Items.Select((item,i)=>
new[] {
new XElement("Item" + (i + 1), item.Name),
new XElement("Price" + (i + 1), item.Price)}))
.ToString();
Can you please have a look on my article, [^]
As an example you can look into the below code. The Serialize method is given on the article.
var test = new ShoppingItems()
{
CustomerName = "test",
Address = "testAddress",
Items = new List<Item>()
{
new Item(){ Name = "item1", Price = "12"},
new Item(){Name = "item2",Price = "14"}
},
};
var xmlData = Serialize(test);
And it will return the string given below,
<?xml version="1.0" encoding="utf-16"?>
<ShoppingItems xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CustomerName>test</CustomerName>
<Address>testAddress</Address>
<Items>
<Item>
<Name>item1</Name>
<Price>12</Price>
</Item>
<Item>
<Name>item2</Name>
<Price>14</Price>
</Item>
</Items>
</ShoppingItems>

Linq to Xml selecting elements

I have this xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Calendar>
<item id="34">
<Date>26 Apr</Date>
<Name>aa</Name>
<Date>26 Apr</Date>
<Name>aaa</Name>
<Date>23 Apr</Date>
<Name>aaaa</Name>
<Date>23 Apr</Date>
<Name>aaaaa</Name>
</item>
<item id="35">
<Date>27 Apr</Date>
<Name>aa</Name>
<Date>27 Apr</Date>
<Name>aaa</Name>
<Date>27 Apr</Date>
<Name>aaaa</Name>
<Date>27 Apr</Date>
<Name>aaaaa</Name>
</item>
</Calendar>
this is my class
public class Calendar
{
public string Name{ get; set; }
public string Data { get; set; }
}
listBox.ItemsSource =
from var in xml.Descendants("item")
orderby Convert.ToInt32(var.Attribute("id").Value) ascending
select new Calendar
{
Name= var.Element("Name").Value,
Data = var.Element("Data ").Value,
};
but in listBox i have only the first date and name of every item
class Program
{
static void Main(string[] args)
{
XDocument xml =
XDocument.Load(
#"Path to your xml");
var q = from x in xml.Descendants("item")
orderby Convert.ToInt32(x.Attribute("id").Value) ascending
select new Calendar
{
Name = x.Elements("Name").Select(a => a.Value).ToList<String>(),
Date = x.Elements("Date").Select(a => a.Value).ToList<String>()
};
List<Calendar> calendars = q.ToList<Calendar>();
}
public class Calendar
{
public List<String> Name { get; set; }
public List<String> Date { get; set; }
}
}
XElement.Element(elementName) only gets the first element that matches the elementName. Try a different approach using XElement.Elements(elementName)

Categories

Resources