C# Deserialize XML elements with attributes into List - c#

Here's the XML:
<xml id = "1234">
<connect id="2"/>
<connect id="1"/>
<connect id="21"/>
<connect id="3"/>
<connect id="7"/>
</xml>
Currently I am doing this:
public class xml
{
//Constructor
[XmlAttribute ("id")]
public uint id;
[XmlElement ("connect")]
public List<Connection> Connections { get; set; }
//Deserializer
}
public class Connection
{
[XmlAttribute ("id")]
public uint id { get; set; }
}
The goal is to get rid of the Connection class entirely and Deserialize the xml straight into:
List<uint> connections;

First, your XML is not valid, i guess it's just a typo - there no end tag for "connect".
I recommend you to use linq XDocument.
Then it's easy:
XDocument xdoc = XDocument.Parse(xml);
List<uint> list = xdoc
.Descendants("connect")
.Select(node => uint.Parse(node.Attribute("id").Value))
.ToList();

Related

How do you deserialize XML with incremented ID on the collection?

I have a set of large XML files with various sections in them.
As an example file I have mocked this up which serializes and deserializes just fine if I don't have the appended counter of 1, 2, 3... etc.
<?xml version="1.0" encoding="utf-16"?>
<employees xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<category name="TestCategory" baseicon="0" decalicon="0">
<EmployeeList1>
<name type="string">Peter Parker</name>
<age type="number">25</age>
</EmployeeList1>
<EmployeeList2>
<name type="string">J.J. Jameson</name>
<age type="number">57</age>
</EmployeeList2>
</category>
</employees>
However, when I have the incremented elements in the collection I get back no objects in the EmployeeList collection but will get back the overall Employees object.
So all the objects within the EmployeeList elements have the same structure overall although some will have optional fields like, let's say "homeowner".
[XmlRoot(ElementName = "category")]
public class Category
{
[XmlAttribute(AttributeName = "name")]
public string Name;
[XmlAttribute(AttributeName = "baseicon")]
public int Baseicon;
[XmlAttribute(AttributeName = "decalicon")]
public int Decalicon;
[XmlElement]
public List<Employee> EmployeeList { get; set; }
public Category()
{
}
}
And the Employee class looks like this:
[XmlRoot(ElementName = "employees")]
public class Employees
{
[XmlElement(ElementName = "category")]
public Category Category;
public Employees()
{
}
}
[XmlRoot(ElementName = "employee")]
public class Employee
{
[XmlElement(ElementName = "name")]
public Name Name { get; set; }
[XmlElement(ElementName = "age")]
public Age Age { get; set; }
}
Then, of course the follow up is how to serialize a collection of elements with an incremented ID?
I can offer a custom XmlReader that will replace the specified element names on the fly.
public class ReplacingXmlReader : XmlTextReader
{
private readonly string _nameToReplace;
public ReplacingXmlReader(string url, string nameToReplace)
: base(url)
{
_nameToReplace = nameToReplace;
}
// Define the remaining constructors here.
public override string LocalName
{
get
{
if (base.LocalName.StartsWith(_nameToReplace))
return _nameToReplace;
return base.LocalName;
}
}
}
Usage is easy:
var xs = new XmlSerializer(typeof(Employees));
using var reader = new ReplacingXmlReader("test.xml", "EmployeeList");
var employees = (Employees)xs.Deserialize(reader);
If necessary, you can make a list of element names to replace.
Using serial numbers as part of the element name is really bad XML design, and the best way of dealing with badly designed XML is often to put it through an XSLT transformation that turns it into something more manageable, before doing any further processing. In this case you might simply strip off the numeric suffixes, because they're completely redundant.
In XSLT 3.0 that would be:
<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="shallow-copy">
<xsl:template match="*[starts-with(local-name(), 'EmployeeList'>
<EmployeeList>
<xsl:apply-templates/>
</EmployeeList>
</xsl:template>
</xsl:transform>

Writing into xml with more than one lines

I am developing a c# application.
I need to write company-product details in this form. One company may have more than one product and one xml file may contain only 1 company. So one xml file for every company.
<?xml version="1.0" encoding="utf-8" ?>
<list>
<CompanyName>Test</CompanyName>
<CompanyID>TestID</CompanyID>
<Product>
<ProductName>Prod</ProductName>
<ProductID>ProdID</ProductID>
<Expire>10.10.2010</Expire>
</Product>
<Product>
<ProductName>Prod2</ProductName>
<ProductID>ProdID2</ProductID>
<Expire>11.10.2010</Expire>
</Product>
<Product>
<ProductName>Prod3</ProductName>
<ProductID>ProdID3</ProductID>
<Expire>12.10.2010</Expire>
</Product>
</list>
How can i make one xml file to have 3 attributes in c#?
I would appreciate your helps.
Regards
If you are asking how to write an xml file in C# then you have write a class provide xml attributes to class and its properties, and then serialize the class.
Instance of the class with all its data will be serialized to xml:
Here is an example in your situation.
[XmlType(TypeName = "CompanyXml")]
public class Company : ISerializable
{
[XmlElement("Product")]
public List<Product> ListProduct { get; set; }
[XmlElement("CompanyName")]
public string CompanyName { get; set; }
[XmlElement("CompanyID")]
public string CompanyID { get; set; }
}
Product class looks like:
[XmlType(TypeName = "Product")]
public class Product : ISerializable
{
[XmlElement("ProductName")]
public string ProductName { get; set; }
[XmlElement("ProductID")]
public string ProductID { get; set; }
[XmlElement("Expires")]
public string Expires { get; set; }
}
Serialization code could follow as:
using (StringWriter stringWriter = new StringWriter())
{
XmlSerializer serializer = new XmlSerializer(typeof(Company));
serializer.Serialize(stringWriter, companyInstance);
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(stringWriter.ToString());
}

Xml serialization - Change the serialization type of xml

I have a struct like this:
public struct Vehicles
{
public string Name { get; set; }
public string Count { get; set; }
public List<Car> Cars { get; set; }
}
public struct Car
{
public string Name { get; set; }
public int Count { get; set; }
public List<Tire> Tires { get; set; }
}
public struct Tire
{
public string Brand { get; set; }
public int Count { get; set; }
public int UniqueCount { get; set; }
public List<Dimension> Dimensions { get; set; }
}
public struct Dimension
{
public string Size { get; set; }
public int AlternateSize { get; set; }
}
When I serialize "Vehicles" it is like:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org /2001/XMLSchema">
<Vehicles>
<Name>SuperVehicles</Name>
<Cars>
<Car>
<Name>BMW</Name>
<Count>29</Count>
<Tires>
<Tire>
<Name>DMZ</Name>
<Count>26</Count>
<UniqueCount>24</UniqueCount>
<Dimensions>
<Dimension>
<Size>70x570</Size>
<AlternateSize>70x580</AlternateSize>
</Dimension>
<Dimension>
<Size>60x570</Size>
<AlternateSize>60x580</AlternateSize>
</Dimension>
<Dimension>
<Size>50x570</Size>
<AlternateSize>50x580</AlternateSize>
</Dimension>
</Dimensions>
</Tire>
</Tires>
</Car>
</Cars>
</Vehicles>
</root>
Now the problem is, I want to serialize it like this:
<root>
<vehicles vehicleName="superVehicles" vehicleCount="50" carName="BMW"
carCount="25" tireBrand="kamu" tireCount="15" tireUniqueCount="15"
dimensionSize="70x570" dimensionAlternateSize="70x580" />
<vehicles vehicleName="superVehicles" vehicleCount="35" carName="MERCEDES"
carCount="22" tireBrand="kamu" tireCount="12" tireUniqueCount="12"
dimensionSize="60x570" dimensionAlternateSize="60x580" />
<vehicles vehicleName="superVehicles" vehicleCount="35" carName="PORSCHE"
carCount="22" tireBrand="kamu" tireCount="12" tireUniqueCount="12"
dimensionSize="60x570" dimensionAlternateSize="60x580" />
</root>
Do I have to change the structure and avoid the groupings or is there any way to create a schema for xml serialization to gather this result.
Summary:
I get all the child items in a new tag when I serialize the root struct to xml but I need to take them as properties of an instance that create only the count of root (Vehicles in this situation) element of rows to xml.
You need to do manual serialization.
Here is how you can implement this using System.Xml.Linq :
var xmlElementsVehicles = new[]{
new XElement("vehicles ", new object[]
{
new XElement("vehicleName", "superVehicles"),
new XElement("vehicleCount", 35),
new XElement("carName", "PORSCHE"),
new XElement("carCount", 2)
}),
new XElement("vehicles ", new object[]
{
new XElement("vehicleName", "superVehicles"),
new XElement("vehicleCount", 35),
new XElement("carName", "PORSCHE"),
new XElement("carCount", 2)
})
};
var root = new XElement("root", xmlElementsVehicles );
var myXml = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), root);
using (var xmlWriter = XmlWriter.Create(stream))
{
myXml.Save(xmlWriter);
}
To use XmlSerializer the model must roughly be the same as the layout; a few things can change (names, etc). However, your model is nothing like the XML. Three options, then:
create a second DTO model that looks like the XML (you can use xsd.exe on the sample XML to automate this), and use XmlSerializer
don't use XmlSerializer, but build the XML somehow else (XmlDocument or XDocument would be the obvious two, or XmlWriter if the size is very large)
use something like xslt to reshape the XML after writing
There is nothing "easy" can be done to make XmlSerializer write that model into your desired XML.

How to parse XML to an IList<BusinessObject> using XPath in C#?

I have the following XML in a string:
<RootElement>
<Data>
<Row>
<Id>1</Id>
<Name>Foo</Name>
</Row>
<Row>
<Id>2</Id>
<Name>Bar</Name>
</Row>
</Data>
</RootElement>
And the following class:
public class BusinessObject
{
public int Id { get; set; }
public string Name { get; set; }
}
How can i parse all the data in the Row elements to an IList using XPath?
I need to learn this for training.
Thanks for your answers.
IEnumerable<BusinessObject> ParseWithXPath(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
foreach (XmlNode node in doc.DocumentElement.SelectNodes("Data/Row")) // XPath query
{
yield return new BusinessObject
{
Id = Int32.Parse(node.SelectSingleNode("Id").InnerText),
Name = node.SelectSingleNode("Name").InnerText
};
}
}
Usage:
IEnumerable<BusinessObject> seq = ParseWithXPath(xml); // .NET 2.0+
IList<BusinessObject> list = new List<BusinessObject>(seq); // .NET 2.0+
I see you've already found a rather clean solution while I was coding an example for you.
Perhaps this helps you a bit more:
internal static class XMLToListWithXPathExample
{
static XmlDocument xmlDocument;
static List<BusinessObject> listBusinessObjects;
static string sXpathStatement = "/Data/Row";
static void LoadXMLData(string p_sXMLDocumentPath)
{
xmlDocument = new XmlDocument(); // setup the XmlDocument
xmlDocument.Load(p_sXMLDocumentPath); // load the Xml data
}
static void XMLDocumentToList()
{
listBusinessObjects = new List<BusinessObject>(); // setup the list
foreach (XmlNode xmlNode in xmlDocument.SelectNodes(sXpathStatement)) // loop through each node
{
listBusinessObjects.Add( // and add it to the list
new BusinessObject(
int.Parse(xmlNode.SelectSingleNode("Id").InnerText), // select the Id node
xmlNode.SelectSingleNode("Name").InnerText)); // select the Name node
}
}
}
public class BusinessObject
{
public int Id { get; set; }
public string Name { get; set; }
// constructor
public BusinessObject(int p_iID, string p_sName)
{
Id = p_iID;
Name = p_sName;
}
}
Regards,
Nico
IEnumerable<BusinessObject> busObjects = from item in doc.Descendants("Row")
select new BusinessObject((int)item.Element("Id"), (string)item.Element("Name"));
You could try something like the above. Your BusinessObject Constructor should take the two parameters. BusinessObject(int id, string name)
Please take a look at the following link for example on Linq to XML.

Getting rid of an array name in C# XML Serialization

I'm trying to get to this result while serializing XML:
<Root Name="blah">
<SomeKey>Eldad</SomeKey>
<Element>1</Element>
<Element>2</Element>
<Element>3</Element>
<Element>4</Element>
</root>
Or in other words - I'm trying to contain an array within the "root" element, alongside additional keys.
This is my crude attempt:
[XmlRootAttribute(ElementName="Root", IsNullable=false)]
public class RootNode
{
[XmlAttribute("Name")]
public string Name { get; set; }
public string SomeKey { get; set; }
[XmlArrayItem("Element")]
public List<int> Elements { get; set; }
}
And my serialization:
string result;
XmlSerializer serializer = new XmlSerializer(root.GetType());
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
serializer.Serialize(sw, root);
result = sw.ToString();
}
However, this is my result (Removed the namespace for clarity):
<Root>
<SomeKey>Eldad</SomeKey>
<Elements>
<Element>1</Element>
<Element>2</Element>
<Element>3</Element>
</Elements>
</Root>
Is there any way to remove the "Elements" part?
Use XmlElement attribute on the Array, this will instruct the serializer to serialize the array items as child elements of the current element and not create a new root element for the array.
[XmlRootAttribute(ElementName="Root", IsNullable=false)]
public class RootNode
{
[XmlAttribute("Name")]
public string Name { get; set; }
public string SomeKey { get; set; }
[XmlElement("Element")]
public List<int> Elements { get; set; }
}
Thanks to Chris Taylor for the answer to my problem too. Using an asmx web service, I was getting the following XML:
<Manufacturers>
<Manufacturer>
<string>Bosch</string>
<string>Siemens</string>
</Manufacturer>
</Manufacturers>
I wanted to get the manufacturer names directly in the the element, getting rid of the element like this:
<Manufacturers>
<Manufacturer>Bosch</Manufacturer>
<Manufacturer>Siemens</Manufacturer>
</Manufacturers>
For anyone else with the same problem, my code to achieve this (in VB.Net) is:
<WebMethod()> _
Public Function GetManufacturers() As Manufacturers
Dim result As New Manufacturers
result.Manufacturer.Add("Bosch")
result.Manufacturer.Add("Siemens")
Return result
End Function
<XmlRoot(ElementName:="Manufacturers")> _
Public Class Manufacturers
<XmlElement("Manufacturer")> _
Public Manufacturer As New List(Of String)
End Class

Categories