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

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>

Related

Deserializing XML with empty array returns an array with a single object

When deserializing an XML containing an empty array I expect this array to be null. Instead, I get an array with a single object with all properties set to null.
Classes:
[XmlRoot(ElementName = "item")]
public class Item
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
}
[XmlRoot(ElementName = "thing")]
public class Thing
{
[XmlElement(ElementName = "items")]
public Item[] Items { get; set; }
}
XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<thing>
<items/>
</thing>
Test:
[Fact]
public void DeserializeTest()
{
var xml = ""; // XML here
var serializer = new XmlSerializer(typeof(Thing));
using (TextReader reader = new StringReader(xml))
{
var thing = serializer.Deserialize(reader) as Thing;
thing.Items.Should().BeNull(); // fails
}
}
What I get:
I must be missing something?
You should use XmlArray attribute to define your array. This declaration should work:
[XmlRoot("item")]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
[XmlRoot(ElementName = "thing")]
public class Thing
{
[XmlArray("items")]
public Item[] Items { get; set; }
}
Here is what I believe is the problem: You are mapping the tag <Items> (with a s) to a object with an property called Items (with a s) of the type array of Item (without a s) that is mapped to the tag <Item> (without a s), and this is causing a confusion.
It does make sense that there is a Item there because you do have an <Items/> tag in your XML. And it does make sense that this Item is null because there is nothing inside the tag <Items/>
I think a XML with items in it would be something like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<thing>
<items>
<item name="some name"/>
</items>
</thing>
In this case your Items would be a one item list with an item object. Do understand what I mean?
Try running your code with more than one <Items> tag or without any and you will see the result.

Get null by deserialize in C# from XML if string between tags

i need some help, i get allways null if i will deserialize a string between two xml tags.
The following example xml file:
<?xml version="1.0" encoding="utf-8" ?>
<item name='First Item' size='1'>
<Bits value='0'>
1st String
</Bits>
<Bits value='1'>
2nd String
</Bits>
</item>
And i write the following classes:
[Serializable()]
public class Bits
{
[XmlElement(ElementName = "Bits")]
public String entryString { get; set; }
[XmlAttribute("value")]
public int entryValue { get; set; }
}
[Serializable()]
[XmlRoot("item")]
public class itemsReader
{
public itemsReader()
{
_bitList = new List<Bits>();
}
[XmlElement("Bits")]
public List<Bits> _bitList { get; set; }
[XmlAttribute("name")]
public String entryName { get; set; }
[XmlAttribute("size")]
public int entrySize { get; set; }
}
Only the entryString is always null!
To readout the XML file i use the following:
itemsReader ireader = null;
String path = #"PathtoString";
XmlSerializer serializer = new XmlSerializer(typeof(itemsReader));
var reader = File.OpenText(path);
ireader = (itemsReader)serializer.Deserialize(reader);
Thanks for help!
You have decorated entryString with XmlElement attribute which will search for an element Bits inside Bits itself.
But you want inner text of Bits element. For that we have another attribute called XmlText.
So change this
[XmlElement(ElementName = "Bits")]
public String entryString { get; set; }
to
[XmlText]
public String entryString { get; set; }
i ran your code on your input and got the output, but the i saw, in the Bits class, that the property entryString has an attribute
[XmlElement(ElementName = "Bits")]
but in your xml file there is no <Bits> tags around 1st String. so i added them and got:
<?xml version="1.0"?>
<item name="First Item" size="2">
<Bits value="1">
<Bits>firstOne</Bits>
</Bits>
<Bits value="2">
<Bits>secOne</Bits>
</Bits>
</item>
and then your code worked
so you can change the xml file or get rid of the attribute. your call

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());
}

Deserialize XML to object (need to return a list of objects)

Started practicing with XML and C# and I have an error message of "There is an error in XML document (3,2)". After looking at the file, I can't see anything wrong with it (Mind you, I probably missed something since I'm a noob). I'm using a Console Application for C# right now. I'm trying to return a list of Adventurers and just a side note, the GEAR element is optional. Here is what I have so far:
XML File - Test1
<?xml version="1.0" encoding="utf-8"?>
<Catalog>
<Adventurer>
<ID>001</ID>
<Name>John Smith</Name>
<Address>123 Fake Street</Address>
<Phone>123-456-7890</Phone>
<Gear>
<Attack>
<Item>
<IName>Sword</IName>
<IPrice>15.00</IPrice>
</Item>
<Item>
<IName>Wand</IName>
<IPrice>20.00</IPrice>
</Item>
</Attack>
<Defense>
<Item>
<IName>Shield</IName>
<IPrice>5.00</IPrice>
</Item>
</Defense>
</Gear>
</Adventurer>
<Adventurer>
<ID>002</ID>
<Name>Guy noone likes</Name>
<Address>Some Big House</Address>
<Phone>666-666-6666</Phone>
<Gear></Gear>
</Adventurer>
</Catalog>
C# Classes
public class Catalog
{
List<Adventurer> Adventurers { get; set; }
}
public class Adventurer
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public Gear Gear { get; set; }
}
public class Gear
{
public List<Item> Attack { get; set; }
public List<Item> Defense { get; set; }
}
public class Item
{
public string IName { get; set; }
public decimal IPrice { get; set; }
}
Serialize Function - Where the Problem Occurs at Line 5
Catalog obj = null;
string path = #"C:\Users\Blah\Desktop\test1.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Catalog));
StreamReader reader = new StreamReader(path);
obj = (Catalog)serializer.Deserialize(reader);
reader.Close();
Console.ReadLine();
The issue is the list of Adventurers in Catalog:
<?xml version="1.0" encoding="utf-8"?>
<Catalog>
<Adventurers> <!-- you're missing this -->
<Adventurer>
</Adventurer>
...
<Adventurer>
</Adventurer>
</Adventurers> <!-- and missing this -->
</Catalog>
You don't have the wrapping element for the Adventurers collection.
EDIT: By the way, I find the easiest way to build the XML structure and make sure it's compatible is to create the object(s) in C#, then run through the built-in XmlSerializer and use its XML output as a basis for any XML I create rather than forming it by hand.
First, "Adventurers" property is not public, it's inaccessible, I think that the best way to find the error is to serialize your object and then compare the result with your xml file.
Your XML doesn't quite line up with your objects... namely these two...
public string City { get; set; }
and
<Address>123 Fake Street</Address>
Change City to Address or vice versa and it should fix the problem.
Edit: Got this to work in a test project, combination of all our answers...
Add <Adventurers> tag after <Catalog> (and </Adventurers> before </Catalog>) and change
List<Adventurer> Adventurers { get; set; }
to
public List<Adventurer> Adventurers { get; set; }
and it works properly for me.
I was able to get your xml to deserialize with a couple minor changes (namely the public qualifier on Adventurer).
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
namespace TempSandbox
{
[XmlRoot]
public class Catalog
{
[XmlElement("Adventurer")]
public List<Adventurer> Adventurers;
private readonly static Type[] myTypes = new Type[] { typeof(Adventurer), typeof(Gear), typeof(Item) };
private readonly static XmlSerializer mySerializer = new XmlSerializer(typeof(Catalog), myTypes);
public static Catalog Deserialize(string xml)
{
return (Catalog)Utils.Deserialize(mySerializer, xml, Encoding.UTF8);
}
}
[XmlRoot]
public class Adventurer
{
public int ID;
public string Name;
public string Address;
public string Phone;
[XmlElement(IsNullable = true)]
public Gear Gear;
}
[XmlRoot]
public class Gear
{
public List<Item> Attack;
public List<Item> Defense;
}
[XmlRoot]
public class Item
{
public string IName;
public decimal IPrice;
}
}
I'm using [XmlElement("Adventurer")] because the xml element names don't exactly match the class property names.
NOTE: I'm using a generic deserialization utility i already had on hand .

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.

Categories