C#: XML To Type -LINQ - c#

How to convert XML Elements to of type Person ?
Elements :
XElement persons = XElement.Parse(
#"<persons>
<person>
<id>10001</id>
<name> Daniel </name>
</person>
<person>
<id>10002</id>
<name>Marshal</name>
</person>
<person>
<id>10003</id>
<name>Leo</name>
</person>
</persons>"
);
Person Type:
class Person
{
int personID;
string name;
public int PersonID
{
get {return personID;}
set {personID = value;}
}
public string Name
{
get {return name;}
set {name = value;}
}
}
I tried (incomplete & not sure whether valid approach )
Person[] Prns = from perns in persons.Nodes select new {perns};

var personList =
from p in persons.Elements("person")
select new Person
{
Name = p.Element("name").Value,
PersonID = int.Parse(p.Element("id").Value)
};

Another option would be to use XML serialization, but using LINQ to XML is probably easier:
[XmlType("person")]
public class Person
{
int personID;
string name;
[XmlElement("id")]
public int PersonID
{
get { return personID; }
set { personID = value; }
}
[XmlElement("name")]
public string Name
{
get { return name; }
set { name = value; }
}
}
…
var serializer = new XmlSerializer(typeof(Person[]), new XmlRootAttribute("persons"));
var result = (Person[])serializer.Deserialize(new StringReader(xml));

Related

How to Deserialize a custom object having dictionary as a member?

I need to Deserialize object having 2 fields as string and one dictionary, i tried to convert but it throwing error as "Cannot serialize member MVVMDemo.Model.Student.books of type System.Collections.Generic.Dictionary"
it is doable when there is no dictionary item in but when i add that dictionary item it fail to convert. throwing error from below line
XmlSerializer serializer = new XmlSerializer(typeof(Student));
Here is my class
public class Student
{
private string firstName;
private string lastName;
public Dictionary<string, string> books;
public Dictionary<string, string> Books
{
get{return books;}
set{books = value;}
}
public string FirstName
{
get{return firstName;}
set
{
if (firstName != value)
{
firstName = value;
}
}
}
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
}
}
}
}
You can solve this with Newtonsoft.Json library. To serialize start with converting your object to json, then use DeserializeXNode method:
var student = new Student()
{
FirstName = "FirstName",
LastName = "LastName",
Books = new Dictionary<string, string>
{
{ "abc", "42" },
}
};
var json = JsonConvert.SerializeObject(student);
var xml = JsonConvert.DeserializeXNode(json, "Root");
var result = xml.ToString(SaveOptions.None);
// result is "<Root><books><abc>42</abc></books><Books><abc>42</abc></Books><FirstName>FirstName</FirstName><LastName>LastName</LastName></Root>"
To deserialize you can use SerializeXmlNode method:
var input = "<Root><books><abc>42</abc></books><Books><abc>42</abc></Books><FirstName>FirstName</FirstName><LastName>LastName</LastName></Root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(input);
var json = JsonConvert.SerializeXmlNode(doc);
var template = new {Root = (Student) null};
var result = JsonConvert.DeserializeAnonymousType(json, template);
// result.Root is an instance of Student class
The reason why Dictionary is not supported by the XmlSerializer is that types such as Dictionary, HashTable, etc. needs an equality comparer which can’t be serialized into XML easily. To get around this problem
Use DataContractSerizalizer
[DataContract]
public class Student
{
private string firstName;
private string lastName;
private Dictionary<string, string> books = new Dictionary<string, string>();
[DataMember]
public Dictionary<string, string> Books
{
get => books;
set => books = value;
}
[DataMember]
public string FirstName
{
get { return firstName; }
set
{
if (firstName != value)
{
firstName = value;
}
}
}
[DataMember]
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
}
}
}
}
var serializer = new DataContractSerializer(typeof(Student));
using (var sw = new StringWriter()){
using (var writer = new XmlTextWriter(sw))
{
serializer.WriteObject(writer, student);
writer.Flush();
var xml = sw.ToString();
}
}

C# Xml Deserialize, ignore data in XML

During de-serialization, how do I ignore a property? In my case, I don't want the FullName property to get initialized at all. I am not looking for [XMLIgnore] solutions - think it as a scenario where I don't have access to change the class.
Here's my class:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
}
Here's how I am initializing:
Person p1 = new Person() { Id = 1, FullName = "P1" };
Person p2 = new Person() { Id = 2, FullName = "P2" };
List<Person> persons = new List<Person> { p, q }; //this object is xml serialized.
And here's my XML: ( I got it though the XML Serialization)
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Person>
<Id>1</Id>
<FullName>P1</FullName>
</Person>
<Person>
<Id>2</Id>
<FullName>P2</FullName>
</Person>
</ArrayOfPerson>
You can use a custom XmlReader in the deserialization process that will simply skip over the FullName elements. Something like this:
public class MyXmlReader : XmlTextReader
{
public MyXmlReader(string filePath) : base(filePath)
{
}
public override bool Read()
{
if (base.Read())
{
if (Name == "FullName")
return base.Read();
return true;
}
return false;
}
}
Then use it like this
var serializer = new XmlSerializer(typeof(List<Person>));
using (var reader = new MyXmlReader("XMLFile.xml"))
{
var person = (List<Person>)serializer.Deserialize(reader);
}
You can implement a different constructor to take a stream or whatever you have. It doesn't have to be a file path.

How do i add data from XML to list<>?

I try to read from an xml file, but its very clonky and a lot of the data I get is in bunch from a child. I get the Name, Age, and so on in one and therefor I can't add it to a list.
My xml-file looks like this:
<?xml version="1.0" encoding="UTF-8"?><People>
<Person>
<Age>30</Age>
<Name>Boy</Name>
<Sex>Male</Sex>
</Person>
<Person>
<Age>28</Age>
<Name>Girl</Name>
<Sex>Female</Sex>
</Person>
And in my xaml.cs file I have:
List<listTest> a = new List<listTest>();
var localFolder = ApplicationData.Current.LocalFolder;
XmlDocument xmlDocument;
var file = await localFolder.TryGetItemAsync("FoodData.xml") as IStorageFile;
xmlDocument = await XmlDocument.LoadFromFileAsync(file);
And with that I need to make a setup where I can take data from the XML and put it into list<> like this:
a.add(listTest {Name = "*DATA FROM XML*", Age ="*DATA FROM XML*", Sex="*DATA FROM XML*"});
I have tried to use LINQ and use p.NodeName == "xxx" to make searches, but I don't seem to get any data out.
Can some one show me how to get the data from my xml to a list?
Let's assume you have this class:
public class Person
{
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
}
Then, to load your XML file, you could do something like:
var doc = XDocument.Load("path to your file");
var people = doc.Root
.Descendants("person")
.Select(node => new Person
{
Name = node.Element("name").Value,
Sex = node.Element("sex").Value,
Age = int.Parse(node.Element("age").Value)
})
.ToList();
See https://msdn.microsoft.com/en-us/library/bb353813.aspx
Here is a simple example of an XML import. After this code executes, results will reflect if people were found (true or false), and msg will be a list of error messages (or empty if success).
var results = true;
var msg = new List<string>(0);
XDocument aDocument = null;
try
{
aDocument = XDocument.Load("");
}
catch (Exception e)
{
results = false;
msg.Add(string.Format("Unable to open file:{0}", ""));
msg.Add(e.Message);
}
if (aDocument != null)
{
var thePeople = aDocument.Descendants("Person").ToArray();
if (thePeople.Any())
{
// there were people in the file. People is an array of XML Nodes containing your person.
foreach (var pers in thePeople.Select(p => new Person().FromXML(p)))
{
// here is a person
}
}
else
{
results = false;
msg.Add("No people found.");
}
}
Hope this helps.
Addition.
You could do something like this in your Person Class. I've added code to the original to illustrate usage.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Sex { get; set; }
public XElement ToXML()
{
return new XElement("Person", "Name", Name,
new XElement("Age", Age),
new XElement("Sex", Sex));
}
public Person FromXML(XElement node)
{
try { Name = node.Element("Name").Value; }
catch { Name = "Not Found"; }
try { Age = Convert.ToInt16(node.Element("Age").Value); }
catch { Age = -1; }
try { Sex = node.Element("Sex").Value; }
catch { Sex = ""; }
return this;
}
}

XPath matches for a XML serialized DataContract class

I have a DataContract class MyDataContract. I am serializing it to a XML file. Later, at a totally different "place" in my application I am loading this file. There I want to verify to load only, if the content of the file matches special conditions. Let's say I store a person and the association to the person's vehicle assuming a person can own just one vehicle. :-)
Here the DataContract classes:
namespace Test.DataContracts
{
[DataContract]
public class MyDataContract
{
[DataMember]
public string Identifier { get; set; }
[DataMember]
public Common.Person Person { get; set; }
[DataMember]
public Common.Vehicle Vehicle { get; set; }
}
}
namespace Test.DataContracts.Common
{
[DataContract]
public class Person
{
[DataMember]
public Global.Gender Gender { get; set; }
[DataMember]
public string Info { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Vehicle
{
[DataMember]
public string Info { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Vendor { get; set; }
}
}
namespace Test.DataContracts.Global
{
[DataContract]
public class Gender
{
[DataMember]
public int Type { get; set; }
[DataMember]
public string Name { get; set; }
}
}
Results in the following serialized XML:
<?xml version="1.0" encoding="utf-8"?>
<MyDataContract xmlns="http://schemas.datacontract.org/2004/07/Test.DataContracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Identifier>123456789</Identifier>
<Person xmlns:a="http://schemas.datacontract.org/2004/07/Test.DataContracts.Common">
<a:Gender xmlns:b="http://schemas.datacontract.org/2004/07/Test.DataContracts.Global">
<b:Name>Female</b:Name>
<b:Type>0</b:Type>
</a:Gender>
<a:Info>She is a beautiful lady.</a:Info>
<a:Name>Jane Doe</a:Name>
</Person>
<Vehicle xmlns:a="http://schemas.datacontract.org/2004/07/Test.DataContracts.Common">
<a:Info>A super great car.</a:Info>
<a:Name>Mustang 1983 Turbo GT</a:Name>
<a:Vendor>Ford</a:Vendor>
</Vehicle>
</MyDataContract>
Now I want to filter out only Female (Type = 0) persons that own any Ford (Vendor = Ford) vehicle. I tried the following, but it always results in false for the matches.
string path = #"c:\janedoe.xml";
var xmlDoc = new XmlDocument();
xmlDoc.Load(path);
XmlNodeList xNodes = xmlDoc.SelectNodes(#"//namespace::*[not(. = ../../namespace::*)]");
var xNamespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
foreach (XmlNode node in xNodes)
{
if (!string.IsNullOrWhiteSpace(xNamespaceManager.LookupNamespace(node.LocalName))) continue;
xNamespaceManager.AddNamespace(node.LocalName, node.Value);
}
using (var fs = new FileStream(path, FileMode.Open))
{
var xDocument = new XPathDocument(fs);
var xNavigator = xDocument.CreateNavigator();
XPathExpression exp1 = xNavigator.Compile(string.Format("MyDataContract/Person/Gender/Type/descendant::*[contains(text(), '{0}')]", "0"));
exp1.SetContext(xNamespaceManager);
XPathExpression exp2 = xNavigator.Compile(string.Format("MyDataContract/Vehicle/Vendor/descendant::*[contains(text(), '{0}')]", "Ford"));
exp2.SetContext(xNamespaceManager);
if (xNavigator.Matches(exp1) && xNavigator.Matches(exp2))
{
Console.WriteLine("File '{0}' indicates a female person that owns a vehicle of Ford.", path);
}
else
{
Console.WriteLine("File '{0}' has no matches (female and Ford).", path);
}
}
Can anyone help?
UPDATE 1 - I have changed the code using XmlNamespaceManager. But still results in false when executing xNavigator.Matches(exp1).
If you have XDocument it is easier to use LINQ-to-XML:
var xdoc = XDocument.Load(memorystream);
// Making it simple, grab the first
var type = xdoc.Descendants(XName.Get("Type","http://schemas.datacontract.org/2004/07/Test.DataContracts.Global")).FirstOrDefault();
var vendor = xdoc.Descendants(XName.Get("Vendor", "http://schemas.datacontract.org/2004/07/Test.DataContracts.Common")).FirstOrDefault();
string path = "blah";
if (type != null && type.Value == "0" && vendor != null && vendor.Value == "Ford")
{
Console.WriteLine("File '{0}' indicates a female person that owns a vehicle of Ford.", path);
}
else
{
Console.WriteLine("File '{0}' has no matches (female and Ford).", path);
}
If you are sure that XPath is the only solution you need:
using System.Xml.XPath;
var document = XDocument.Load(fileName);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("a", "http://schemas.datacontract.org/2004/07/Test.DataContracts.Global");
var name = document.XPathSelectElement("path", namespaceManager).Value;

LinqToXML Why this doesn't work?

I have my sample XML file:
<?xml version="1.0" encoding="utf-8" ?>
<contacts>
<contact contactId="2">
<firstName>Barney</firstName>
<lastName>Gottshall</lastName>
</contact>
<contact contactId="3">
<firstName>Armando</firstName>
<lastName>Valdes</lastName>
</contact>
<contact contactId="4">
<firstName>Adam</firstName>
<lastName>Gauwain</lastName>
</contact>
</contacts>
and my Program:
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
public class Program
{
public class contact
{
public int contactId { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public override string ToString()
{
return firstName+" "+lastName;
}
}
public static void Main()
{
string test;
XDocument xDocument = XDocument.Load("items.xml");
var all = from a in xDocument.Elements("contact")
where a.Attribute("contactId")!=null
select new contact
{
contactId = (int) a.Attribute("contactId"),
firstName = (string) a.Attribute("firstName"),
lastName = (string) a.Attribute("lastName")
};
if (all == null)
Console.WriteLine("Null !");
else
foreach (contact t in all)
{
test = t.ToString();
Console.WriteLine(t.ToString());
}
}
Console.ReadKey();
}
}
This code shows me blank console window. There's no "Null !" and no contact element. I spent a lot of time thinking why is that... could someone help me? when I put breakpoint inside foreach statement it's not working
There are actually a couple of reasons.
Elements only gives you top-level elements of the document (the "contacts" element), so you should use Descendants to get lower-level elements).
firstName and lastName are elements, not attributes.
Here's working code:
var all = from a in xDocument.Descendants("contact")
where a.Attribute("contactId")!=null
select new contact
{
contactId = (int) a.Attribute("contactId"),
firstName = (string) a.Element("firstName").Value,
lastName = (string) a.Element("lastName").Value
};
Try Descendants() instead of Elements()
var all = from a in xDocument.Descendants("contact")
where a.Attribute("contactId") != null
select new contact
{
contactId = (int) a.Attribute("contactId"),
firstName = (string) a.Attribute("firstName"),
lastName = (string) a.Attribute("lastName")
};
or Element("contacts").Elements("contact")
var all = from a in xDocument.Element("contacts").Elements("contact")
where a.Attribute("contactId") != null
select new contact
{
contactId = (int) a.Attribute("contactId"),
firstName = (string) a.Attribute("firstName"),
lastName = (string) a.Attribute("lastName")
};

Categories

Resources