XmlSerializer filter by namespace - c#

I am trying to deserialise an RSS 2.0 feed and i would like to take into account some of the iTunes extensions, but not have to bake them directly into the main class.
With the XML deserialiser in C# would something like the following be possible?
public class RssChannel
{
[XmlElement("title")]
public string Title { get; set; }
[XmlElement("link")]
public string Link { get; set; }
....
[XmlElement(Namespace = "itunes")]
public iTunesExtensions iTunes { get; set; }
}
public class iTunesExtensions
{
[XmlElement("category")]
public string[] Categories { get; set; }
}
Which i am hoping would parse something like:
<channel>
<itunes:category text="Society & Culture"/>
<itunes:category text="Society & Culture"/>
<itunes:category text="Society & Culture"/>
</channel>
Is it possible to do something like this where it is more modular? Or am i stuck baking it into the main class?

bizzehdee,
In order to set the first level to "", you would need to set it as the root and name of your class:
[XmlRoot("channel")]
public class channel
I will keep it as RssChannel, for the following examples.
I am assuming you intend to use more platforms than iTunes, so iTunes still has its own class. The way to do this, with less code, is to use a list instead of an array:
public List<iTunes> iTunes;
Categories will have its own class, so that you can use categories with any platform. Notice the use of XmlAttribute. This will include the name of the category in the same line:
public class iTunes
{
public List<Category> Categories { get; set; }
}
public class Category
{
[XmlAttribute("category")]
public string category { get; set; }
}
You can use a static class to help you serialize and deserialize the data. Here are two methods in a static class that will help:
// Save XML data.
public static void SaveData(ref RssChannel instance) {}
// Retrieve XML data.
public static RssChannel DeserializeData() {}
The best way to use these methods is to first get an instance of the RssChannel with the DeserializeData() method, like:
RssChannel foo = StaticClassName.DeserializeData();
Make your changes to it, then save that instance by passing it as a reference to the SaveData() method, like:
SaveData(ref foo);
Here is a full working example:
public static class XML
{
// Serializes the passed instance of RssChannel into XML file, and saves to runtime memory.
public static void SaveData(ref RssChannel instance)
{
// Objects:
StreamWriter sw = new StreamWriter("yourXmlFile.xml");
XmlSerializer serializer = new XmlSerializer(typeof(RssChannel));
// Save data.
serializer.Serialize(sw, instance);
sw.Close();
}
// Deserializes data from the XML file, and returns the instance.
public static RssChannel DeserializeData()
{
// Objects:
RssChannel channelData = new RssChannel();
XmlSerializer serializer = new XmlSerializer(typeof(RssChannel));
List<iTunes> iTunesList = new List<iTunes>();
if (File.Exists("yourXmlFile.xml"))
{
FileStream stream = new FileStream("yourXmlFile.xml", FileMode.Open);
// Deserialize data.
channelData = (RssChannel)serializer.Deserialize(stream);
stream.Close();
// Add data from deserialized iTunes list to list instance.
if (channelData.iTunesList != null)
iTunesList = channelData.iTunesList;
}
// Add data to RootData object lists.
channelData.iTunesList = iTunesList;
return channelData;
}
}
[XmlRoot("RssChannel")]
public class RssChannel
{
[XmlAttribute("Title")]
public string Title; // { get; set; }
[XmlAttribute("Link")]
public string Link; // { get; set; }
public List<iTunes> iTunesList; // { get; set; }
}
public class iTunes
{
public List<Category> Categories; // { get; set; }
}
public class Category
{
[XmlAttribute("category")]
public string category; // { get; set; }
}
You can use the classes and static methods like this:
private void AnyMethod()
{
// To get an instance of your RssChannel class with all the data:
RssChannel rssChannel = XML.DeserializeData();
// Do anything with the data. Example below:
iTunes newITunes = new iTunes();
List<Category> categoryList = new List<Category>();
Category newCategory1 = new Category(); // Create new categories.
newCategory1.category = "Allegro";
categoryList.Add(newCategory1);
Category newCategory2 = new Category();
newCategory2.category = "Prestissimo";
categoryList.Add(newCategory2);
newITunes.Categories = categoryList; // Add the categories to list.
rssChannel.iTunesList.Add(newITunes); // Add that list to iTunes list.
// Now, to save the data, pass a reference to the instance we just worked on:
XML.SaveData(ref rssChannel);
}
This will produce a file that looks like:
<?xml version="1.0" encoding="utf-8"?>
<RssChannel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<iTunesList>
<iTunes>
<Categories>
<Category category="Allegro" />
<Category category="Prestissimo" />
</Categories>
</iTunes>
</iTunesList>
</RssChannel>

Related

Parsing XML into classes using LINQ in C#

I'm having a lot of trouble parsing an XML document into my custom classes. I've tried to read what I can find on the web and on here, but I'm still not getting anywhere. I'm working on a real estate app, and am trying to model a basic property where you have:
1 property
1 property can have multiple buildings
Each building can have multiple tenants.
I decided to try to store the data in an xml document, and I made an example as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Property>
<Name>Grove Center</Name>
<Building>
<Name>Building1</Name>
<Tenant>
<Name>Tenant1</Name>
<SquareFeet>2300</SquareFeet>
<Rent>34000</Rent>
</Tenant>
<Tenant>
<Name>Tenant2</Name>
<SquareFeet>3100</SquareFeet>
<Rent>42000</Rent>
</Tenant>
<Tenant>
<Name>Tenant3</Name>
<SquareFeet>1700</SquareFeet>
<Rent>29000</Rent>
</Tenant>
</Building>
<Building>
<Name>Building2</Name>
<Tenant>
<Name>Tenant1</Name>
<SquareFeet>6150</SquareFeet>
<Rent>80000</Rent>
</Tenant>
<Tenant>
<Name>Tenant2</Name>
<SquareFeet>4763</SquareFeet>
<Rent>60000</Rent>
</Tenant>
</Building>
</Property>
Actually my first question is if this format is even correct.. I saw some xml examples where they added an extra tag such as <buildings> before they started listing out the individual <Building> tags for each building. Is that necessary? The W3C examples I saw didn't do it that way.. but this post on stackexchange was pretty close to what im doing: Parsing XML with Linq with multiple descendants
Here is the code for my classes in C#:
public class Property
{
public string Name { get; set; }
public List<Building> Buildings = new List<Building>();
}
public class Building
{
public string Name { get; set; }
public List<Tenant> Tenants = new List<Tenant>();
}
public class Tenant
{
public string Name { get; set; }
public int SF { get; set; }
public decimal Rent { get; set; }
}
I'm not sure if using the new keyword on my lists right in the class definition is good practice.. but I was getting errors trying to add a building or tenant to the list later on in my program so I didn't know what else to do. Right now I'm not much further in my main code than:
Property p = new Property();
XDocument doc = XDocument.Load(#"C:\Users\SampleUser\Desktop\sample-property.xml");
Any help is appreciated, thanks
Following query will give you the correct result:-
Property p = new Property
{
Name = (string)doc.Root.Element("Name"),
Buildings = doc.Root.Elements("Building")
.Select(x => new Building
{
Name = (string)x.Element("Name"),
Tenants = x.Elements("Tenant")
.Select(t => new Tenant
{
Name = (string)t.Element("Name"),
SF = (int)t.Element("SquareFeet"),
Rent = (decimal)t.Element("Rent")
}).ToList()
}).ToList()
};
Theres a few things you might want to change.
The property names must match the xml tags, or you have to specify the mapping manually. In your example code, Buildings and Tenants are declared as fields, you should change it to properties. If you want, you can then initialize them to empty list in the constructors:
public class Property
{
public string Name { get; set; }
[XmlElement("Building")]
public List<Building> Buildings { get; set; }
public Property()
{
Buildings = new List<Building>();
}
}
public class Building
{
public string Name { get; set; }
[XmlElement("Tenant")]
public List<Tenant> Tenants { get; set; }
public Building()
{
Tenants = new List<Tenant>();
}
}
public class Tenant
{
public string Name { get; set; }
[XmlAttribute("SquareFeet")]
public int SF { get; set; }
public decimal Rent { get; set; }
}
Further, I would recommend deserializing the file rather than using linq. Consider these helper methods:
public static class XmlHelper
{
public static T DeserializeFromXmlString<T>(string xml)
{
var xmlSerializer = new XmlSerializer(typeof (T));
using (var stringReader = new StringReader(xml))
{
return (T) xmlSerializer.Deserialize(stringReader);
}
}
public static T DeserializeFromXmlFile<T>(string filename) where T : new()
{
return DeserializeFromXmlString<T>(File.ReadAllText(filename));
}
}
Deserialization is then easy:
var listOfProperties = XmlHelper.DeserializeFromXmlFile<Property>(#"C:\Users\SampleUser\Desktop\sample-property.xml");
Intializing your public fields with empty lists is perfectly fine and good practice to avoid the errors you got. If you do not initialize them, they are null, hence the errors.
You could use properties instead of fields for your lists however.
Starting with C# 6 you can use simplified auto-property assignment:
public List<Building> Buildings {get;set;} = new List<Building>();
For C# < 6 you can use auto properties and initialize the property within the constructor or use a property with backing field.
//Auto property with assignment in constructor
public class Property
{
public string Name { get; set; }
public List<Building> Buildings {get;set;};
public Property(){
Buildings = new List<Building>();
}
}
//Property with backing field
public class Property
{
private List<Building> _buildings = new List<Building>();
public string Name { get; set; }
public List<Building> Buildings {get {return _buildings;} set {_buildings = value;}};
}
For reading XML and creating the object graph, you can use LINQ in conjuction with object initializers.
Func<IEnumerable<XElement>, IEnumerable<Tenant>> getTenants = elements => {
return elements.Select (e => new Tenant {
Name = e.Element("Name").Value,
Rent = decimal.Parse(e.Element("Rent").Value),
SF = int.Parse(e.Element("SquareFeet").Value)
});
};
Func<IEnumerable<XElement>, IEnumerable<Building>> getBuildings = elements => {
return elements.Select (e => new Building{
Name = e.Element("Name").Value,
Tenants = getTenants(e.Elements("Tenant")).ToList()
});
};
//xdoc is your parsed XML document
//e.g. var xdoc = XDdocument.Parse("xml contents here");
var property = new Property{
Name = xdoc.Root.Element("Name").Value,
Buildings = getBuildings(xdoc.Root.Elements("Building")).ToList()
};

Populate custom List sub class from XML document via LINQ

I have figured out how to populate a custom class from XML data, but I ran into an issue along the way. Things were working perfectly with my existing method of populating data until I was thrown a bit of a curve ball. The new schema I was sent is similar to this:
<ITEM_REPLY>
<TRAN_ID>1320691307345</TRAN_ID>
<REPLY_CODE>0</REPLY_CODE>
<UNIT_PRICE>8.2784</UNIT_PRICE>
<SUP_LOCS>
<SUP_LOC>
<SUP_LOC_ID>001134</SUP_LOC_ID>
<COUNTRY_ID>USA</COUNTRY_ID>
<QTY_AVL>47.000</QTY_AVL>
<ITEM_UOM>EA</ITEM_UOM>
</SUP_LOC>
<SUP_LOC>
<SUP_LOC_ID>006817</SUP_LOC_ID>
<COUNTRY_ID>USA</COUNTRY_ID>
<QTY_AVL>20.000</QTY_AVL>
<ITEM_UOM>EA</ITEM_UOM>
</SUP_LOC>
</SUP_LOCS>
<MESSAGE />
<QTY_BREAKS />
</ITEM_REPLY>
Pretty standard XML schema, problem is I'm not sure how to populate my custom class with it. Here's what I do have:
static void Main(string[] args)
{
var order = ConvertXMLMessage<ItemReply>(request);
}
protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
var xml = new XmlDocument();
xml.LoadXml(xmlData);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
{
T work = (T)(serializer.Deserialize(xmlReader));
return work;
}
}
public class ItemReply
{
[XmlElement("ITEM_REPLY")]
public ItemAvlReply ITEM_REPLY { get; set; }
}
public class ItemAvlReply
{
[XmlElement("TRAN_ID")]
public string TRAN_ID { get; set; }
[XmlElement("REPLY_CODE")]
public string REPLY_CODE { get; set; }
[XmlElement("UNIT_PRICE")]
public string UNIT_PRICE { get; set; }
[XmlElement("SUP_LOCS")]
public SupplierLocations SUP_LOCS;
[XmlElement("MESSAGE")]
public string MESSAGE { get; set; }
[XmlElement("QTY_BREAKS")]
public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
[XmlElement("SUP_LOC")]
public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
[XmlElement("SUP_LOC_ID")]
public string SUP_LOC_ID { get; set; }
[XmlElement("COUNTRY_ID")]
public string COUNTRY_ID { get; set; }
[XmlElement("QTY_AVL")]
public string QTY_AVL { get; set; }
[XmlElement("ITEM_UOM")]
public string ITEM_UOM { get; set; }
}
This works perfectly minus the List<Item> part. I'm not overly experienced with LINQ and I'm not sure how to go about declaring a sub array in my class via this statement. I am also open to a different approach from creating the List<Item> part, I'm just not sure where to start otherwise. Is there a better approach for what I'm need to do? Is there an easy solution I am just unaware of in LINQ?
Here's a simple way to do it, assuming the example XML file you provided has typos. I assumed the OrderId has a closing tag, and that the closing tag for Items should be /Items.
Here's the version of the xml I used:
<Order>
<TransactionID>123</TransactionID>
<OrderID>1</OrderID>
<Items Number="2">
<Item>
<ItemName>Test</ItemName>
<Color>Red</Color>
</Item>
<Item>
<ItemName>Test1</ItemName>
<Color>Blue</Color>
</Item>
</Items>
</Order>
Here's the code to read/write the XML: (the xml variable is a String)
var order = ConvertXMLMessage<Order>(xml);
WriteXMLFile<Order>(order, #"test.xml");
Here's the ConvertXMLMessage and WriteXMLFile functions:
protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
var xml = new XmlDocument();
xml.LoadXml(xmlData);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
{
T work = (T)(serializer.Deserialize(xmlReader));
return work;
}
}
protected static void WriteXMLFile<T>(T item, String saveLocation) where T : class, new()
{
System.Xml.Serialization.XmlSerializer writer = new System.Xml.Serialization.XmlSerializer(typeof(T));
System.IO.StreamWriter file = new System.IO.StreamWriter(saveLocation);
writer.Serialize(file, item);
file.Close();
}
and here's the class structure:
public class Order
{
[XmlElement("TransactionID")]
public string TransactionId { get; set; }
[XmlElement("OrderID")]
public string OrderId { get; set; }
[XmlElement("Items")]
public ItemsContainer Items;
}
public class ItemsContainer
{
[XmlAttribute("Number")]
public Int32 Number { get; set; }
[XmlElement("Item")]
public List<Item> Items;
}
public class Item
{
[XmlElement("ItemName")]
public string ItemName { get; set; }
[XmlElement("Color")]
public string Color { get; set; }
}
As you'll notice I added some attributes to let the XML parser know how to handle the class when it's converting from/to the XML. I also added another small class called "ItemsContainer" just to hold the details on the Items tag. If you didn't need the "Number" attribute, then you could probably find a way to do away with this. However, this should get you in the right direction.
The example I provided is a simple version of how I usually handle the situation, obviously there's some improvements you can make depending on your needs.
Edit
I changed the Item class to use ItemName instead of TransactionId. It was an oversight on my part.
Edit 2
Here's the corrections you need to make to the newly posted code. The reason the Order class worked in the previous example was it matched the root XML element. You're new XML does align with the base class. So we need to add in a couple more attributes to make this work. You can also remove your ItemReply class. It's not needed.
So here's the new classes:
[XmlRoot("ITEM_REPLY")]
public class ItemAvlReply
{
[XmlElement("TRAN_ID")]
public string TRAN_ID { get; set; }
[XmlElement("REPLY_CODE")]
public string REPLY_CODE { get; set; }
[XmlElement("UNIT_PRICE")]
public string UNIT_PRICE { get; set; }
[XmlElement("SUP_LOCS")]
public SupplierLocations SUP_LOCS;
[XmlElement("MESSAGE")]
public string MESSAGE { get; set; }
[XmlElement("QTY_BREAKS")]
public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
[XmlElement("SUP_LOC")]
public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
[XmlElement("SUP_LOC_ID")]
public string SUP_LOC_ID { get; set; }
[XmlElement("COUNTRY_ID")]
public string COUNTRY_ID { get; set; }
[XmlElement("QTY_AVL")]
public string QTY_AVL { get; set; }
[XmlElement("ITEM_UOM")]
public string ITEM_UOM { get; set; }
}
Everything else should remain the same. The parsing/converting the XML to classes should work without any changes.

C# XML Deserialize Array Element Values

I've managed to deserialize my XML for the most part, but I'm stuck on one particular thing. How do I get the value of an element, if it's in an array, and each item in that array has it's own attributes. Let me show you what I have
<BusinessObject Name="BusinessName" RecID="12345">
<FieldList>
<Field Name="Field1">FieldValue1</Field>
<Field Name="Field2">FieldValue2</Field>
</FieldList>
</BusinessObject>
So this is a cut-down version, but shows the basis of the XML. I'm currently having trouble trying to capture "FieldValue1" and "FieldValue2" in their respective Field elements.
[XmlRoot("BusinessObject")]
public sealed class BusinessObject
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlAttribute("RecID")]
public string RecID { get; set; }
[XmlElement("FieldList", Type = typeof(FieldList))]
public FieldList FieldList { get; set; }
public BusinessObject()
{
FieldList = null;
}
public static BusinessObject FromXmlString(string xmlString)
{
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(BusinessObject));
var instance = (BusinessObject)serializer.Deserialize(reader);
return instance;
}
}
[Serializable]
public class FieldList
{
[XmlElement("Field", Type = typeof(Field))]
public Field[] Fields { get; set; }
public FieldList()
{
Fields = null;
}
}
[Serializable]
public class Field
{
[XmlAttribute("Name")]
public string Name { get; set; }
public Field()
{
}
}
I'm sure it's just something simple that I'm missing, but I was having trouble trying to put my problem into words to perform a relevant search.
Many thanks,
Mark
Thanks for taking a look. Clearly I should play around more before posting here, as I've just managed to get it working. I had to add the following to the Field class:
[Serializable]
public class Field
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlText]
public string Value { get; set; }
public Field()
{
}
}
Unfortunately I'm just running on a copy of express here, so I couldn't work out how to use xsd without the command prompt.
Cheers all,
Mark

Array deserialization only returning 1 element

As the title says when i deserialize the following file i only get the first (and always the first) element:
<?xml version="1.0"?>
<ServerConnections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Entries>
<ServerConnectionEntry>
<Name>Local</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
<ServerConnectionEntry>
<Name>Local2</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
</Entries>
</ServerConnections>
My code to deserialize:
var list = (ServerConnections)mSerializer.Deserialize(inputStream)).Entries;
and list.Count then is 1. ServerConnections looks like that:
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
There is no exception happening.
Edit:
The problem occurs when I include my class that does custom xml serialization (implements IXmlSerializable). What it does is the following:
void BigNumber::ReadXml(System::Xml::XmlReader^ reader) {
reader->ReadStartElement();
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
cli::array<Byte>^ data = (cli::array<Byte>^)serializer->Deserialize(reader);
pin_ptr<unsigned char> ptr(&data[0]);
BN_bin2bn(ptr, data->Length, mNumber);
}
void BigNumber::WriteXml(System::Xml::XmlWriter^ writer) {
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
serializer->Serialize(writer, ToByteArray());
}
While data contains the correct data after ReadXml the deserializer that works the whole list stops and does not read any additional elements.
Same here, this seems to work fine for me using code similar to yours.
public class Program
{
static void Main(string[] args)
{
XmlSerializer deserializer = new XmlSerializer(typeof(ServerConnections));
var reader = new StreamReader(#"../../Test.xml");
var entries = (ServerConnections)deserializer.Deserialize(reader);
reader.Close();
}
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
public class ServerConnectionEntry
{
public string Name { get; set; }
public string Host { get; set; }
public string Port { get; set; }
public string Username { get; set; }
public BinaryCode AuthHash { get; set; }
}
public class BinaryCode
{
[XmlElement("base64Binary")]
public string Code { get; set; }
}
}
I do not see any issue. I've even reproduced your scenario (complete test enclosed below) of your code, and it is doing its job correctly.
Try to search elsewhere (e.g. assure that the passed xml is the one you are expecting). But serialization is working correctly with your C# class mapping
EDIT: AuthHash class no does the conversion for you from byte[] to base64 and back
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
public class ServerConnectionEntry
{
public string Name { get; set; }
public AuthHash AuthHash { get; set; }
}
public class AuthHash
{
[XmlIgnore]
public byte[] Hash { get; set; }
public string base64Binary
{
get { return Convert.ToBase64String(Hash); }
set { Hash = Convert.FromBase64String(value); }
}
}
[TestClass]
public class DeserializationTest
{
public const string MyXml = #"<?xml version=""1.0""?>
<ServerConnections
xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Entries>
<ServerConnectionEntry>
<Name>Local</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
<ServerConnectionEntry>
<Name>Local2</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
</Entries>
</ServerConnections>
";
[TestMethod]
public void Deserialization_Has_Two_Elements()
{
TextReader reader = new StringReader(MyXml);
var mySerializer = new XmlSerializer(typeof(ServerConnections));
var list = ((ServerConnections)mySerializer.Deserialize(reader)).Entries;
Assert.IsTrue(list.Count() == 2);
Assert.IsTrue(list.First().Name == "Local");
Assert.IsTrue(list.Last().Name == "Local2");
Assert.IsTrue(list.First().AuthHash.Hash.Length > 0);
Assert.IsTrue(list.Last().AuthHash.Hash.Length > 0);
}
}
Well fizzlesticks, the problem was that i forgot one tiny little line in the deserialization. It should be like that:
void BigNumber::ReadXml(System::Xml::XmlReader^ reader) {
reader->ReadStartElement();
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
cli::array<Byte>^ data = (cli::array<Byte>^)serializer->Deserialize(reader);
pin_ptr<unsigned char> ptr(&data[0]);
BN_bin2bn(ptr, data->Length, mNumber);
reader->ReadEndElement();
}
The ReadEndElement makes sure it advances to the next node. As i didn't do that the deserializer above had a problem but instead of throwing an exception it just stops parsing and returns what it got so far...

.NET XML Serialization and Null Collections

I have a class with some collections in them, and I would like to serialize instances of this class to XML without having to initialize the collections to be empty, and without having to implement IXmlSerializable. I don't care if it creates empty elements, or doesn't create the elements at all. Just that it works without having to initialize a collection for each collection based property.
I have look at all the XML attributes I can decorate the properties with, and have not had any success with this. This seems like a simple thing to do that is can have an element or just none at all. Then when it is being deserialized it would just leave them null or ignore them period.
Here is a simple version of a class to use for working through this issue. Using this and the defaults you get an exception "Object reference not set to an instance of an object" due to the collections being null;
public class MyClass
{
public string Name { get; set; }
public bool IsAlive { get; set; }
public List<Car> Cars { get; set; }
public List<Home> Homes { get; set; }
public List<Pet> Pets { get; set; }
public void ToXmlFile(string fileName)
{
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
TextWriter writer = new StreamWriter(fileName);
serializer.Serialize(writer, this);
writer.Close();
}
}
EDIT
Thanks for the helps guys, it turns out the issue was in my GetHashCode method which didn't handle the null correctly. Once I fixed this all was good. I marked the first one to answer as being correct. Sorry for the Red Herring, but working through it with you guys did help.
You do not need to initialize collections in order to serialize the class to XML. Here's a simple program to demonstrate:
class Program
{
static void Main(string[] args)
{
MyClass c = new MyClass() { Name = "Test", IsAlive = true };
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
using (MemoryStream ms = new MemoryStream())
{
serializer.Serialize(ms, c);
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
}
Console.ReadLine();
}
}
This will print the following output:
<?xml version="1.0"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Test</Name>
<IsAlive>true</IsAlive>
</MyClass>
In other words, null collection properties behave the same way as any other null property - they don't get serialized at all (unless you change the default value, which I haven't).
You can serialize this without a problem only you have to send the types of car, home and pet when you serialize in the extraTypes parameter.
You mention an exception during (de)serialization; what you have should work OK (at least, according to how you define the question). I wonder if you actually have:
public class MyClass
{
public string Name { get; set; }
public bool IsAlive { get; set; }
public List<Car> Cars { get; private set; }
public List<Home> Homes { get; private set; }
public List<Pet> Pets { get; private set; }
}
(not uncommon to not want a public setter)
Well, XmlSerializer is fussy about this; you either need a public setter, or (as somebody pointed out recently) no setter; for example:
public class MyClass
{
public string Name { get; set; }
public bool IsAlive { get; set; }
private readonly List<Car> cars = new List<Car>();
public List<Car> Cars { get { return cars; } }
private readonly List<Home> homes = new List<Home>();
public List<Home> Homes { get { return homes; } }
private readonly List<Pet> pets = new List<Pet>();
public List<Pet> Pets { get { return pets; } }
}
I think setting the return type of the XMLSerialisation method to a bool would allow you to handle errors in a safer fashion as opposed to the current method:
public bool ToXmlFile(string fileName)
{
if(fileName != "")
{
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
TextWriter writer = new StreamWriter(fileName);
serializer.Serialize(writer, this);
writer.Close();
return true;
}
return false;
}
Which can then be used like so:
if(ToXMLFile)
{
MessageBox.Show("Save Succesful");
}
else
(
MessageBox.Show("Save unsuccesful");
)
This is merely a crude example however i have used a similar method in my own applications when loading or saving data and it works well, also a good visual indicator for the end user.

Categories

Resources