I am trying to deserialize some XML into an array of items.
Here's the XML:
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<items>
<item>
<name>John</name>
</item>
<item>
<name>Jane</name>
</item>
</items>
And my class:
[XmlRoot("item")]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
I then deserialize:
var xmlSerializer = new XmlSerializer(typeof(Item[]), new XmlRootAttribute("items"));
using (TextReader textReader = new StringReader(xmlString))
{
var items = (Item[])xmlSerializer.Deserialize(textReader);
var itemCount = items.Length;
}
itemCount is 0 (it should be 2).
There is a similar solution here: https://stackoverflow.com/questions/15544517 but it seem to work only when the XML node names are identical to the class names (mine differ in capitalisation).
What do I need to modify to ensure all the items deserialize?
The Xml Root "items" is missing
Your class should be:
[XmlRoot("items")]
public class Items
{
[XmlElement("item")]
public Item[] Item { get; set; }
}
[XmlRoot("item")]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
And the code to deserialize:
var xmlSerializer = new XmlSerializer(typeof(Items), new XmlRootAttribute("items"));
using (TextReader textReader = new StringReader(xmlString))
{
var items = (Items)xmlSerializer.Deserialize(textReader);
var itemCount = items.Item.Length;
}
The root node is <items>, so the Item class should not have the XmlRoot attribute, instead it should use the XmlType attribute:
[XmlType("item")]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
Related
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.
The code below currently works and loads XML into a class structure. This structure consists of a collection of items (items), each that have static properties and a collection that I use for a variable number of properties.
Two things I want to do:
1) I want to change the list into something that has a unique key. I tried a dictionary and that didn't work, maybe a HashSet...leading on to...
2) The key for the collection should be driven by the item's "id" XML attribute.
I can't quite figure it out, and trying to copy the KVP pattern I used for an items variable parameters does not work. It adds the items to "items" but they are all empty and the hash for the collection is not populated.
Help please
[XmlRoot("ItemsContainer")]
public class Items
{
[XmlAttribute("Version")]
public string Version { get; set; }
[XmlArray("Items")]
[XmlArrayItem("Item")]
public List<Item> items = new List<Item>(); //TODO - The key in this collection should be unique and driven from an item's "Id" XML attribute
public static Items Load(string path)
{
var xml = Resources.Load<TextAsset>(path);
var serializer = new XmlSerializer(typeof (Items));
var reader = new StringReader(xml.text);
var items = (Items) serializer.Deserialize(reader);
reader.Close();
return items;
}
}
public class Item
{
//### Fixed Item Parameters
[XmlAttribute("Id")]
public int Id { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Description")]
public string Description { get; set; }
[XmlElement("Value")]
public float Value { get; set; }
//### Variable Item Parameters as Key Value pairs.
[XmlArray("KVPS")]
[XmlArrayItem("KVP")]
public List<KeyValuePair<string, string>> KVPS { get; set; } //We will have cases were we don't want unique keys
}
[Serializable]
public class KVP<K, V>
{
public K Key { get; set; }
public V Value { get; set; }
public KVP()
{
}
public KVP(K key, V value)
{
Key = key;
Value = value;
}
}
Here is the associated XML
<?xml version="1.0" encoding="utf-8"?>
<ItemsContainer Version="1.0">
<Items>
<Item Id="100">
<Name>Burger</Name>
<Description>The lovely tasting Big Mac</Description>
<Value>5.67</Value>
</Item>
<Item Id="101">
<Name>Sammich</Name>
<Description>Ham and cheese</Description>
<Value>2.80</Value>
</Item>
<Item Id="102">
<Name>Carling</Name>
<Description>Pint of carling</Description>
<Value>2.80</Value>
<KVPS>
<KVP>
<Key>alchohol</Key>
<Value>3.9</Value>
</KVP>
<KVP>
<Key>alchohol</Key>
<Value>4.9</Value>
</KVP>
<KVP>
<Key>alchohol</Key>
<Value>5.9</Value>
</KVP>
</KVPS>
</Item>
</Items>
</ItemsContainer>
Try something like this. I changed StringReader to StreamReader just for my testing. You can change it back
public static Dictionary<int, Item> Load(string path)
{
//var xml = Resources.Load<TextAsset>(path);
XmlSerializer serializer = new XmlSerializer(typeof(Items));
StreamReader reader = new StreamReader(path);
Items items = (Items)serializer.Deserialize(reader);
reader.Close();
Dictionary<int, Item> dict = items.items.GroupBy(x => x.Id).ToDictionary(x => x.Key, y => y.FirstOrDefault());
return dict;
}
I am having problems serializing elements of an XML to a list of objects.
This is the XML:
<result>
<stats>
<numitemsfound>1451</numitemsfound>
<startfrom>0</startfrom>
</stats>
<items>
<item>
<id>1</id>
<markedfordeletion>0</markedfordeletion>
<thumbsrc>
</thumbsrc>
<thumbsrclarge>
</thumbsrclarge>
...
<datasource>65</datasource>
<data>
<amount>100</amount>
<kj>389</kj>
<kcal>92.91</kcal>
<fat_gram>0.2</fat_gram>
<fat_sat_gram>-1</fat_sat_gram>
<kh_gram>20.03</kh_gram>
</data>
<servings>
<serving>
<serving_id>386</serving_id>
<weight_gram>150</weight_gram>
</serving>
</servings>
</item>
</result>
The classes I prepared for serialization are
[XmlType("item")]
public class Item
{
[XmlAttribute("id")]
public string id { get; set; }
[XmlAttribute("markedfordeletion")]
public string markedfordeletion { get; set; }
...
[XmlAttribute("datasource")]
public string datasource { get; set; }
[XmlElement("data")]
public Data data { get; set; }
[XmlElement("servings")]
public List<Serving> servings { get; set; }
}
}
This is how I try to serialize the xml
public void ParseSearch(string xml)
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Item>), new XmlRootAttribute("item"));
StringReader stringReader = new StringReader(xml);
var productList = (List<Item>)serializer.Deserialize(stringReader);
}
But I get the error ----> System.InvalidOperationException : <result xmlns=''> was not expected. Can you please help me solve this problem?
You have to use a serializer which serializes an instance of result, not of type List:
XmlSerializer serializer = new XmlSerializer(typeof(Result), new XmlRootAttribute("result")); //whatever `Result` actually is as type).
You canĀ“t serialize and de-serialize just parts of a document, either the whole one or nothing at all.
So you need a root-type:
[XmlRoot("result")]
public class Result
{
public Stats Stats {get; set;}
[XmlArray("items")]
[XmlArrayItem("item")]
public List<Item> Items { get; set; }
}
I'm trying to deserialize an XML document in C#. The XML document comes form a Web API and the structure can't be changed. The document contains a list of items and each item can be one of four types. The type of each item is defined in a sub element of the class something like this (type names for sake of simplicity):
<?xml version="1.0" encoding="utf-8"?>
<items>
<item>
<type>Car</type>
<make>Ford</make>
<registration>AB00 AAA</registration>
</item>
<item>
<type>Bicycle</type>
<make>Specialized</make>
<frameSerialNo>123456768</frameSerialNo>
</item>
</items>
I want to deserialise this into a set of classes that inherit form an abstract class called Item like this:
abstract class Item
{
public string Make { get; set; }
}
class Bicycle : Item
{
public string FrameSerialNumber { get; set; }
}
class Car : Item
{
public string Registration { get; set; }
}
class ItemList
{
public Item[] Items { get; set; }
}
Is that possible using the System.Xml.Serialization.XmlSerializer class? If so what attributes should I set on my classes to make the inheritance part work?
Not directly, no.
You can either parse all the data manually with XmlDocument, XmlReader, etc. or feed a modified version of the XML to your XmlSerializer.
XmlSerializer would require a xsi:type attribute to be able to directly deserialize that XML. In your case that would look like this:
<Item xsi:type="Car">
Instead of
<Item>
<Type>Car</Type>
</Item>
If you can convert that structure before deserializing it (e.g. by manipulating an XmlDocument and then passing an XmlReader to the XmlSerializer instead of the original stream.
Example:
public static ItemList Load(Stream stream)
{
XmlDocument document = new XmlDocument();
document.Load(stream);
ModifyTypes(document);
XmlReader reader = new XmlNodeReader(document);
XmlSerializer serializer = new XmlSerializer(typeof(ItemList));
return serializer.Deserialize(reader) as ItemList;
}
public static ModifyTypes(XmlDocument document)
{
const string xsiNamespaceUri = "http://www.w3.org/2001/XMLSchema-instance";
XmlNodeList nodes = originalDocument.SelectNodes("//Item");
if (nodes == null) return;
foreach (XmlNode item in nodes)
{
if (item == null) continue;
if (item.Attributes == null) continue;
var typeAttribute = item.Attributes["type", xsiNamespaceUri];
if (typeAttribute != null) continue;
// here you'll have to add some logic to get the actual
// type name based on your structure
XmlAttribute attribute = document.CreateAttribute("xsi", "type", xsiNamespaceUri);
attribute.Value = "Car";
signDefinition.Attributes.Append(attribute);
}
}
Once you converted the data you have two options:
1.) Add an XmlInclude Attribute for each inherited class
[XmlInclude(typeof(Bicycle))]
[XmlInclude(typeof(Car))]
abstract class Item
2.) Explicitly specify all inherited types when serializing
XmlSerializer serializer = new XmlSerializer(typeof(ItemList), new[]{
typeof(Bicycle),
typeof(Car)
});
Another problem you will be facing is the fact, that your data structure is a bit different from your XML.
class ItemList
{
public Item[] Items { get; set; }
}
Serializing this ItemList would usually result in a structure similar to this:
<?xml version="1.0"?>
<ItemList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Items>
<Item>...</Item>
<Item>...</Item>
<Item>...</Item>
</Items>
</ItemList>
So you might want to consider deserializing like this:
class ItemList
{
[XmlArray("Items")]
[XmlArrayItem("Item")]
public Item[] Items { get; set; }
public void Load(Stream stream)
{
//Insert Code options from above here
Items = serializer.Deserializer(typeof(Item[])) as Item[];
}
}
This cannot be done directly with XmlSerializer.
However, there are several ways to do it.
For example, you can create instances of classes and fill their properties manually. This preserves your structure of the classes. Only the array in the ItemList class change to the List for easy adding.
public class ItemList
{
public List<Item> Items { get; set; }
}
ItemList list = new ItemList();
list.Items = new List<Item>();
using (var reader = XmlReader.Create("test.xml"))
{
while (reader.ReadToFollowing("item"))
{
var inner = reader.ReadSubtree();
var item = XElement.Load(inner);
var type = item.Element("type");
if (type.Value == "Car")
{
var car = new Car();
car.Make = item.Element("make").Value;
car.Registration = item.Element("registration").Value;
list.Items.Add(car);
}
else if (type.Value == "Bicycle")
{
var bicycle = new Bicycle();
bicycle.Make = item.Element("make").Value;
bicycle.FrameSerialNumber = item.Element("frameSerialNo").Value;
list.Items.Add(bicycle);
}
}
}
However, if there many class properties and XML nodes respectively, it is quite tedious to manually write a lot of code.
In this case, you can deserialize each class separately. However, it is necessary to add XML attributes to our classes.
[XmlRoot("item")]
public abstract class Item
{
[XmlElement("make")]
public string Make { get; set; }
}
[XmlRoot("item")]
public class Bicycle : Item
{
[XmlElement("frameSerialNo")]
public string FrameSerialNumber { get; set; }
}
[XmlRoot("item")]
public class Car : Item
{
[XmlElement("registration")]
public string Registration { get; set; }
}
public class ItemList
{
public List<Item> Items { get; set; }
}
ItemList list = new ItemList();
list.Items = new List<Item>();
var carSerializer = new XmlSerializer(typeof(Car));
var bicycleSerializer = new XmlSerializer(typeof(Bicycle));
using (var reader = XmlReader.Create("test.xml"))
{
while (reader.ReadToFollowing("item"))
{
var inner = reader.ReadSubtree();
var item = XElement.Load(inner);
var type = item.Element("type");
if (type.Value == "Car")
{
var car = (Car)carSerializer.Deserialize(item.CreateReader());
list.Items.Add(car);
}
else if (type.Value == "Bicycle")
{
var bicycle = (Bicycle)bicycleSerializer.Deserialize(item.CreateReader());
list.Items.Add(bicycle);
}
}
}
Deserialization of XML file with nested array of elements
I've searched but can't see any helpful examples.
this is my XML sample:
<trans>
<orderNo>0001</orderNo>
<orderDate>08/07/2014</orderDate>
<orders>
<item>
<itemName>item1</itemName>
<itemAmount>200</itemAmount>
<itemMeasures>
<measure>each</measure>
<measure>case</measure>
</itemMeasures>
</item>
<item>
<itemName>item2</itemName>
<itemAmount>100</itemAmount>
<itemMeasures>
<measure>each</measure>
<measure>case</measure>
</itemMeasures>
</item>
</orders>
</trans>
You must create classes for each of the structures you have in the XML and then using XmlSerializer and the method Deserialize of the object from that class you can create nested arrays with the values. If you want code and example, please edit your post with the full xml structure.
See the example below for part of your xml:
[XmlType("trans")]
public class Trans
{
[XmlElement("orderNo")]
public string OrderNo { get; set; }
[XmlElement("orderDate")]
public string OrderDate { get; set; }
[XmlArray("orders")]
public HashSet<Item> Orders { get; set; }
}
[XmlType("item")]
public class Item
{
[XmlElement("itemName")]
public string ItemName { get; set; }
[XmlElement("itemAmount")]
public string ItemAmount { get; set; }
}
The the Deserialization code is:
XmlSerializer mySerializer = new XmlSerializer(typeof(Trans[]), new XmlRootAttribute("trans"));
using (FileStream myFileStream = new FileStream("XmlFile.xml", FileMode.Open))
{
Trans[] r;
r = (Trans[])mySerializer.Deserialize(myFileStream);
}