How to Mapping Deserialize XML document using c# - c#

i am new to c # programming and i m stuck in how ot Deserialize this XML document, i have seen this tutorial How to Deserialize XML document and it was helpful but as you can see my XML contains more informations and he is more complex :
<?xml version="1.0" encoding="utf-8"?>
<Classrooms>
<Classroom name="ClassroomA">
<Students name = "John"/>
<Students name = "Bryan"/>
<Students name = "Eva"/>
</Classroom>
<Classroom name="ClassroomB">
<Students name = "Harry"/>
<Students name = "Emma"/>
<Students name = "Joe"/>
</Classroom>
<Classroom name="ClassroomC">
<Students name = "Lionnel"/>
<Students name = "Rio"/>
<Students name = "Eva"/>
</Classroom>
</Classrooms>
My main goal is to create a Map of classrooms from my XML file :
example : Dictionnary<string,List> classrooms ,
Key 1 : classroomA, Values : John,Bryan,Eva
Key 2 : classroomB, Values : Harry,Emma,Joe
Key 3 : classroomC, Values : Lionnel,Rio,Eva
Thanks for help

Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Linq;
namespace ConsoleApplication178
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(Classrooms));
Classrooms classrooms = (Classrooms)serializer.Deserialize(reader);
reader.Close();
//using xml linq
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, List<string>> dict = doc.Descendants("Classroom")
.GroupBy(x => (string)x.Attribute("name"), y => y)
.ToDictionary(x => x.Key, y => y.Elements("Students").Select(x => (string)x.Attribute("name")).ToList());
}
}
public class Classrooms
{
[XmlElement()]
public List<Classroom> Classroom { get; set; }
}
public class Classroom
{
[XmlAttribute]
public string name { get; set; }
[XmlElement()]
public List<Students> Students { get; set; }
}
public class Students
{
[XmlAttribute]
public string name { get; set; }
}
}

Related

C# and LINQ.XML Select from XML

I've been struggling to find out how to select multiple values from an XML file, compare them to a special value and then do something. So far I just managed to select a single value but I also need a different one in the same select, I hope you can assist me
XML Structure
<?xml version="1.0" encoding="utf-8"?>
<userConnectionSettings version="1" lastApplicationUrl="xxx" lastIdentity="yyy">
<application url="xxx" lastFolderId="zzz">
<user name="test" domain="domain.tld" lastFolderId="yyy" />
</application>
</userConnectionSettings>
Now basically, what i want to do is read the lastApplicationURL and the domain value. I managed to do the lastApplicationURL but i can't seem to select the domain and i don't know how to get that value properly. Here's my code :
XDocument foDoc = XDocument.Load(FrontOfficePath);
foreach (var FOurl in foDoc.Descendants("userConnectionSettings"))
{
string FOappURL = (string)FOurl.Attribute("lastApplicationUrl");
if (FOappURL == "something")
{
TODO
}
else
{
TODO
}
}
You can select domain attribute, in two different ways :
1 - Like #Juharr comment :
foreach (var FOurl in foDoc.Descendants("userConnectionSettings"))
{
string domain = FOurl
.Element("application")
.Element("user")
.Attribute("domain")
.Value;
....
}
Or, by getting descendant of application and select the first item, like :
foreach (var FOurl in foDoc.Descendants("userConnectionSettings"))
{
string domain = FOurl.Descendants("application")
.Select(x => x.Element("user").Attribute("domain").Value)
.FirstOrDefault();
....
}
i hope you find this helpful.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<Application> applications = doc.Descendants("application").Select(x => new Application()
{
url = (string)x.Attribute("url"),
id = (string)x.Attribute("lastFolderId"),
name = (string)x.Element("user").Attribute("name"),
domain = (string)x.Element("user").Attribute("domain"),
folder = (string)x.Element("user").Attribute("lastFolderId")
}).ToList();
}
}
public class Application
{
public string url { get; set; }
public string id { get; set; }
public string name { get; set; }
public string domain { get; set; }
public string folder { get; set; }
}
}

Searching for an attribute by its value

I have following XML file:
<?xml version="1.0" encoding="utf-8"?>
<root>
<Communication Id ="456">
<Person> Ivan Ivanov </Person>
<Describtion>
<Age> 16 </Age>
<Place> Moscow </Place>
<Key Name ="Language"> English </Key>
<Key Name ="Profession"> Doctor </Key>
</Describtion>
</Communication>
<Communication Id ="1010">
<Person> Petr Petrov </Person>
<Describtion>
<Age> 21 </Age>
<Place> St.Peterburg </Place>
<Key Name ="Language"> Français </Key>
<Key Name ="Profession"> Ingeneer </Key>
</Describtion>
</Communication>
</root>
There is a list of Key tags with attribute Name which has different value. This value determines which variable the value between tags will be written to. How can I write an algorithm for such a search?
You can search like this.
xmlDoc.SelectNodes("root/Communication/Describtion/Key[#Name=\"Language\"][text()=\" English \"]")
Try with XmlDocument
public static string getNodeValue(XmlDocument xmldoc, string id, string key)
{
return xmldoc.SelectSingleNode($"root/Communication[#Id='{id}']/Describtion/Key[#Name='{key}']")
.InnerText
.Trim();
}
Usage
var xmlDoc = new XmlDocument();
xmlDoc.Load(xmlFile);
Console.WriteLine(getNodeValue(xmlDoc, "456", "Language"));
Console.WriteLine(getNodeValue(xmlDoc, "1010", "Language"));
Output
English
Français
I used Xml Linq along with a dictionary and IEquatable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
StreamReader reader = new StreamReader(FILENAME);
reader.ReadLine(); //allow unicode characters
XDocument doc = XDocument.Load(reader);
List<Person> people = doc.Descendants("Communication").Select(x => new Person()
{
id = (int)x.Attribute("Id"),
name = (string)x.Element("Person"),
age = (int)x.Descendants("Age").FirstOrDefault(),
place = (string)x.Descendants("Place").FirstOrDefault(),
language = ((string)x.Descendants("Key").Where(y => (string)y.Attribute("Name") == "Language").FirstOrDefault()).Trim(),
profession = ((string)x.Descendants("Key").Where(y => (string)y.Attribute("Name") == "Profession").FirstOrDefault()).Trim()
}).ToList();
Dictionary<Person, List<Person>> dict = people
.GroupBy(x => x, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
List<Person> results = dict[new Person() { language = "English", profession = "Doctor" }].ToList();
}
}
public class Person : IEquatable<Person>
{
public int id { get; set; }
public string name { get; set; }
public int age { get; set; }
public string place { get; set; }
public string language { get; set; }
public string profession { get; set; }
public Boolean Equals(Person other)
{
return
(language == other.language) && (profession == other.profession);
}
public override int GetHashCode()
{
return (language + "^" + profession).GetHashCode() ;
}
}
}

Deserializing XML to List<Object> - faster to combine into one xml file first?

I have a list of contracts that come in as xml strings like so:
<contract>
<CONTRACTID>CN0425-3</CONTRACTID>
<NAME>10425 - One-Year Contract</NAME>
<WHENMODIFIED>02/01/2020 08:18:30</WHENMODIFIED>
</contract>
<contract>
<CONTRACTID>CN0260-4</CONTRACTID>
<NAME>10260 - One-Year Contract</NAME>
<WHENMODIFIED>02/01/2020 08:18:30</WHENMODIFIED>
</contract>
I'm using this function to deserialize each item to an object:
public static T ParseXML<T>(this string #this) where T : class
{
var reader = XmlReader.Create(#this.Trim().ToStream(), new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Document });
var xmlRoot = new XmlRootAttribute { ElementName = typeof(T).Name.ToLower(), IsNullable = true };
return new XmlSerializer(typeof(T), xmlRoot).Deserialize(reader) as T;
}
Calling it like so:
// list is of type List<XElement> which contains a list of contracts
contracts.AddRange(from object e in list select e.ToString().ParseXML<Contract>() into e
select new Contract { Key = e.CONTRACTID, Name = e.NAME });
And here is my Contract class:
[SerializableAttribute]
[DesignerCategoryAttribute("code")]
[XmlTypeAttribute(AnonymousType = true)]
[XmlRootAttribute(Namespace = "", IsNullable = false)]
public class Contract
{
public string CONTRACTID { get; set; }
public string NAME { get; set; }
public string WHENMODIFIED { get; set; }
}
The problem is when I have a large list (1000+ contracts), the deserialization process is slow because it has to go through each xml item. I am wondering if it would optimize performance to combine all the xml items into one file and then deserialize the whole thing to a list of objects. I could potentially combine the list of xml items like this:
<contracts>
<contract>
<CONTRACTID>CN0425-3</CONTRACTID>
<NAME>10425 - One-Year Contract</NAME>
<WHENMODIFIED>02/01/2020 08:18:30</WHENMODIFIED>
</contract>
<contract>
<CONTRACTID>CN0260-4</CONTRACTID>
<NAME>10260 - One-Year Contract</NAME>
<WHENMODIFIED>02/01/2020 08:18:30</WHENMODIFIED>
</contract>
</contracts>
Do you guys know if that would benefit performance? And if so, how to combine the list of xml items and deserialize it?
Serialization is slow. Do comparison and see if xml linq below is faster :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var contracts = doc.Descendants("contract").Select(x => new Contract()
{
CONTRACTID = (string)x.Element("CONTRACTID"),
NAME = (string)x.Element("NAME"),
WHENMODIFIED = (DateTime)x.Element("WHENMODIFIED")
});
}
}
public class Contract
{
public string CONTRACTID { get; set; }
public string NAME { get; set; }
public DateTime WHENMODIFIED { get; set; }
}
}

Remove unnecessary nesting when deserializing XML or access data in parent node?

The XML I'm attempting to deserialize has a lot of fields that have the data nested in a subfield and I'd like to assign this data into the parent property on the class rather than create additional unneeded structure.
<?xml version="1.0" encoding="UTF-8"?>
<x:EventRecord eventid="EVR-1000">
<x:Postcode>
<x:ID>ABC123</x:ID>
</x:Postcode>
</x:EventRecord>
I've created an EventRecord class with a Postcode string property:
public class EventRecord
{
public string EventID { get; set; }
public string Postcode { get; set; }
}
Is there an attribute that can decorate the property that can tell the deserializer to take the value out of the nested ID field? There will never be any other fields in <x:Postcode> aside from <x:ID>.
Also is there a way to assign the eventid XML attribute on the parent x:EventRecord node to the EventID property inside itself?
Using following xml :
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="abc">
<x:EventRecord eventid="EVR-1000">
<x:Postcode>
<x:ID>ABC123</x:ID>
</x:Postcode>
</x:EventRecord>
</root>
Code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication16
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XNamespace ns = doc.Root.GetNamespaceOfPrefix("x");
EventRecord record = doc.Descendants(ns + "EventRecord").Select(x => new EventRecord() {
EventID = (string)x.Attribute("eventid"),
Postcode = (string)x.Descendants(ns + "ID").FirstOrDefault()
}).FirstOrDefault();
}
}
public class EventRecord
{
public string EventID { get; set; }
public string Postcode { get; set; }
}
}

XmlSerializer deserializing list with different element names

I am trying to parse XML that has an element that looks like the following using an XmlSerializer. There are quite a few currency types under the amount element and I'd like to deserialize them into a collection of objects that have a string property holding the currency type and an integer property holding the amount.
Is there any clean way of doing this without having to custom parse the amount. I'd like to just apply the XmlSerializer attributes to my classes and get something that works.
I have no control over the output XML.
<root>
<property1>a</property1>
<property1>b</property1>
<property1>c</property1>
<amount>
<EUR type="integer">1000</EUR>
<USD type="integer">1100</USD>
</amount>
<root>
The best way to attack XML deserialization is to start with serialization. To that end, here are some classes with attributes to control XML serialization:
public sealed class root
{
[XmlElement("property1")]
public List<string> property1;
[XmlArrayItem(Type = typeof(EUR))]
[XmlArrayItem(Type = typeof(USD))]
public List<amount> amount;
}
public abstract class amount
{
[XmlAttribute]
public string type { get; set; }
[XmlText]
public string Value { get; set; }
}
public sealed class EUR : amount { }
public sealed class USD : amount { }
Test code is:
var root = new root { property1 = new List<string>(), amount = new List<amount>() };
root.property1.AddRange(new[]{ "a", "b", "c"});
var eur = new EUR { type = "integer", Value = "1000" };
var usd = new USD { type = "integer", Value = "1100" };
root.amount.AddRange(new amount[]{ eur, usd});
which generates the following XML:
<?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">
<property1>a</property1>
<property1>b</property1>
<property1>c</property1>
<amount>
<EUR type="integer">1000</EUR>
<USD type="integer">1100</USD>
</amount>
</root>
I think your best bet will be to do partial XML parsing, and keep the content of the <amount> element as a collection of XmlElements. You'll still have to parse it manually, but you'll only have to parse that part manually. Example:
[XmlRoot("root")]
public class RecordInfo
{
[XmlElement("property1")]
public List<string> Property1;
[XmlElement("amount")]
public AmountRawData AmountData;
}
public class AmountRawData
{
[XmlAnyElement]
public List<XmlElement> Content;
public IEnumerable<AmountInfo> Parse()
{
foreach (var element in this.Content)
{
yield return
new AmountInfo()
{
Currency = element.LocalName,
Type = element.GetAttribute("type"),
Amount = element.InnerText,
};
}
}
}
public class AmountInfo
{
public string Currency;
public string Type;
public string Amount;
}
Example usage:
var serializer = new XmlSerializer(typeof(RecordInfo));
var result = (RecordInfo)serializer.Deserialize(dataStream);
foreach (var amount in result.AmountData.Parse())
Console.WriteLine($"{amount.Currency} {amount.Type} {amount.Amount}");
Answered a similar question last week using xml linq:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Elements().Select(x => new {
property1 = x.Elements("property1").Select(y => (string)y).ToList(),
dictCurrency = x.Elements("amount").Elements().GroupBy(y => y.Name.LocalName, z => (int)z)
.ToDictionary(y => y.Key, z => z.FirstOrDefault())
}).FirstOrDefault();
}
}
}
Here is one other approach using Cinchoo ETL - an open source library, to de-serialize such XML as below
Define POCO object
[ChoXmlRecordObject]
public class Root
{
[ChoXmlNodeRecordField(XPath = "//property1")]
public string[] Properties { get; set; }
[ChoXmlNodeRecordField(XPath = "//amount/*")]
public double[] Amounts { get; set; }
}
Deserialize it using xml reader object
var rec = ChoXmlReader.Deserialize<Root>("*** Xml File Path ***").FirstOrDefault();
Console.WriteLine(rec.Dump());
Disclaimer: I'm the author of this library.

Categories