Serializing class implementing ICollection - c#

I have a collection class implementing ICollection<T> with a few custom attributes thrown in for completeness..
In this simplistic sample, its a simple Request/Results pattern with the request itself being passed back as an attribute of the results class.
[Serializable]
public class MyRequest
{
public int SearchID { get; set; }
}
[Serializable]
public class MyResults : ICollection<MyElement>
{
public MyRequest RequestDetails { get; set; }
private ICollection<MyElement> _list = new List<MyElement>();
/* ICollection interface methods removed */
}
[Serializable]
public class MyElement
{
public int ID { get; set; }
}
Here's the sample program to instantiate and then output a serialized string.
class Program
{
static void Main(string[] args)
{
MyResults m = new MyResults();
m.RequestDetails = new MyRequest() { SearchID = 1 };
for (int i = 1; i <= 5; i++)
{
m.Add(new MyElement { ID = i });
}
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(m.GetType());
using (MemoryStream xmlStream = new MemoryStream())
{
xmlSerializer.Serialize(xmlStream, m);
xmlStream.Position = 0;
xmlDoc.Load(xmlStream);
}
System.Diagnostics.Debug.WriteLine(xmlDoc.OuterXml);
}
}
The problem is that the output is not including the MyRequest details...
<?xml version="1.0"?>
<ArrayOfMyElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyElement>
<ID>1</ID>
</MyElement>
<MyElement>
<ID>2</ID>
</MyElement>
<MyElement>
<ID>3</ID>
</MyElement>
<MyElement>
<ID>4</ID>
</MyElement>
<MyElement>
<ID>5</ID>
</MyElement>
</ArrayOfMyElement>

XmlSerializer always ignores extra properties when serializing a collection; the only way to do it, as far as I know, is not to implement ICollection<MyElement> on your MyResults class, and instead expose the collection as a property:
public class MyResults
{
public MyRequest RequestDetails { get; set; }
public ICollection<MyElement> Items { get; set; }
}
(the Serializable attribute isn't needed for XML serialization)

Just change ICollection to Collection because XmlSerialization does not support Generic Interfaces:
public class MyResults
{
public MyResults()
{
this.Items= new Collection<MyElement>();
}
public MyRequest RequestDetails { get; set; }
public Collection<MyElement> Items { get; set; }
}

Related

XML Serializing information on Base class and inherited class

I am trying to serialize a C# object into XML so that it could be used as the body of API call. They are very particular about the input they need. I have built the following class to hold the data I need to send over to them. Including attributes and all properties as Elements instead of attributes. They also require that lists include the type="array" I though that creating my own class the implements a List would be the easiest since all lists I give them must have the same attribute. When serialization occurs it serializes the base class of List items but it doesn't include the attribute I want from the derived class.
public class CustomArray<T> : List<T>
{
[XmlAttribute]
public string type { get; set; } = "array";
}
[XmlRoot("message")]
public class MessageBody
{
[XmlArray("Checks"), XmlArrayItem("CheckItem")]
public CustomArray<Check> CheckList { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
[XmlArray("InvoiceList"), XmlArrayItem("Invoice")]
public CustomArray<Invoice> InvoiceList { get; set; }
}
public class Invoice
{
[XmlElement("C_INVOICE_ID")]
public long ID { get; set; }
[XmlElement("C_INVOICE_NUM")]
public string InvoiceNum { get; set; }
}
I then run this code:
// Create a sample object
var message = new MessageBody()
{
CheckList = new CustomArray<Check>
{
new Check
{
CheckNumber = "111",
Amount = 1.00M
},
new Check
{
CheckNumber = "112",
Amount = 2.00M,
InvoiceList = new CustomArray<Invoice>
{
new Invoice
{
ID = 1,
InvoiceNum = "1"
}
}
}
}
};
// Create custom settings
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
// Serialize item and print it to console
using (var sw = new StringWriter())
using (var writer = XmlWriter.Create(sw, settings))
{
var serializer = new XmlSerializer(message.GetType());
serializer.Serialize(writer, message, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
Console.WriteLine(sw.ToString());
}
I get this written to the console:
<message>
<Checks>
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList>
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
But I need to get this:
<message>
<Checks type="array">
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList type="array">
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
Thank you for your help!
Here is a dotnetfiddle that I made to show it off. It's not exact but it has the same idea. https://dotnetfiddle.net/ALCX5H
Try following :
[XmlRoot("message")]
public class MessageBody
{
[XmlElement("Checks")]
public Checks Checks { get; set; }
}
public class Checks
{
[XmlAttribute]
public string type { get; set; }
[XmlElement("Checks")]
public List<Check> Checks { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
}

How to Serialize C# Class with class name as root element

I have the following C# class
[XmlRoot("Customer")]
public class MyClass
{
[XmlElement("CustId")]
public int Id {get;set;}
[XmlElement("CustName")]
public string Name {get;set;}
}
I then use the following function serialise the class object to Xml
public static XmlDocument SerializeObjectToXML(object obj, string sElementName)
{
XmlSerializer serializer =
new XmlSerializer(obj.GetType(), new XmlRootAttribute("Response"));
using (MemoryStream ms = new MemoryStream())
{
XmlDocument xmlDoc = new XmlDocument();
serializer.Serialize(ms, obj);
ms.Position = 0;
xmlDoc.Load(ms);
}
}
My current output to XML is like;
<Response>
<CustId></CustId>
<CustName></CustName>
</Response>
But how can I get the response to look like;
<Response>
<Customer>
<CustId></CustId>
<CustName></CustName>
</Customer>
</Response>
Change the XmlElementAttribute on MyClass (it's not actually valid there according to http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlelementattribute(v=vs.110).aspx) to an XmlTypeAttribute:
[XmlType("Customer")]
public class MyClass
{
[XmlElement("CustId")]
public int Id { get; set; }
[XmlElement("CustName")]
public string Name { get; set; }
}
The serialization method can now be (identical to that in the question but without the second parameter in the constructor of XmlSerializer):
public static XmlDocument SerializeObjectToXML(object obj, string sElementName)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType());
XmlDocument xmlDoc = new XmlDocument();
using (MemoryStream ms = new MemoryStream())
{
serializer.Serialize(ms, obj);
ms.Position = 0;
xmlDoc.Load(ms);
}
return xmlDoc;
}
You can create a response object containing your customer, because that is what your desired xml shows as well.
[XmlRoot("Response")]
public class ResponseClass
{
[XmlElement("Customer")]
public Myclass Customer {get;set;}
}
You could define them like this:
public class MyClass
{
[XmlElement("Customer")]
public Customer cust { get; set; }
}
public class Customer
{
[XmlElement("CustId")]
public int Id { get; set; }
[XmlElement("CustName")]
public string Name { get; set; }
}
By the way, the [XmlElement("Customer")] is not valid on your example...

correct serialize but incorrect Deserialize

consider following codes and classes:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
public class Element1
{
[XmlAttribute]
public int Order { get; set; }
public string name1 { get; set; }
public ElementCollcetion collection { get; set; }
}
public class Element2
{
[XmlAttribute]
public int Order { get; set; }
public string name2 { get; set; }
}
public class Elements
{
public Element1 element1 { get; set; }
public Element2 element2 { get; set; }
}
public interface IFoo
{
string FooName { get; set; }
}
public class Foo1 : IFoo
{
public string FooName { get; set; }
public int deff1 { get; set; }
}
public class Foo2 : IFoo
{
public string FooName { get; set; }
public bool deff2 { get; set; }
}
public class ElementCollcetion : List<IFoo>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer serializer = null;
bool flag;
reader.Read();
while (true)
{
flag = false;
if (string.Compare(reader.Name, typeof(Foo1).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo1));
flag = true;
}
else if (string.Compare(reader.Name, typeof(Foo2).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo2));
flag = true;
}
if (flag)
this.Add((IFoo)serializer.Deserialize(reader));
else
break;
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (IFoo foo in this.AsEnumerable())
{
XmlSerializer serializer = new XmlSerializer(foo.GetType());
serializer.Serialize(writer, foo);
}
}
}
class Program
{
static void Main(string[] args)
{
Elements elements = new Elements()
{
element1 = new Element1
{
name1 = "Name1",
Order = 1,
collection = new ElementCollcetion(){
new Foo1{deff1=10,FooName="FooName1"},
new Foo2{deff2=true,FooName="FooName2"}
},
},
element2 = new Element2
{
name2 = "Name2",
Order = 2
}
};
XmlSerializer serializer = new XmlSerializer(typeof(Elements));
TextWriter textWriter = new StreamWriter(#"d:\ser.xml");
serializer.Serialize(textWriter, elements);
textWriter.Close();
TextReader textReader = new StreamReader(#"d:\ser.xml");
Elements element = (Elements)serializer.Deserialize(textReader);
textReader.Close();
}
}
}
when i run it, an xml will generated into ser.xml like so:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element1 Order="1">
<name1>Name1</name1>
<collection>
<Foo1>
<FooName>FooName1</FooName>
<deff1>10</deff1>
</Foo1>
<Foo2>
<FooName>FooName2</FooName>
<deff2>true</deff2>
</Foo2>
</collection>
</element1>
<element2 Order="2">
<name2>Name2</name2>
</element2>
</Elements>
but it cannot correctly Deserialize the file unless i reorder the elements in the xml like so:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element2 Order="2">
<name2>Name2</name2>
</element2>
<element1 Order="1">
<name1>Name1</name1>
<collection>
<Foo1>
<FooName>FooName1</FooName>
<deff1>10</deff1>
</Foo1>
<Foo2>
<FooName>FooName2</FooName>
<deff2>true</deff2>
</Foo2>
</collection>
</element1>
</Elements>
note that serializer.UnknownAttribute and serializer.UnknownElement will not raise during both execution.
what is the problem? and how can i fix it?
---------------EDIT----------------------
i know that problem is in IXmlSerializable.ReadXml() implementation. but what kind of problem and how should i cure it?
Basically, you aren't progressing the reader to the end of the sub-tree correctly. Adding reader.Read(); to the end of ReadXml fixes it, but is a bit ugly; ReadSubtree() may be safer.
Frankly, implementing IXmlSerializable correctly and robustly is hard. I always advise against it.
Although I would have to see your IXmlSerializable implementation, my bet would be, that your ReadXml implementation has a reversed order of processing ... e.g.: it first looks for element2 instead of element1. If this is not it, please post your IXmlSerializable implementation.
EDIT
As Marc pointed out, you need to add another read. Problem was, that XmlReader by calling reader.Read() processed collection entry tag, but at the end of your method, you didn't process the end tag /collection, hence another reader.Read() call. This basically prevented the deserialization to proceed correctly.
Generally the correct pattern for ReadXml implementation is to start with:
bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement(); //Start reading the element
if (isEmpty) //Return on empty element
{
return;
}
And finish with:
reader.ReadEndElement();
Here is also your implementation using the pattern:
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer serializer = null;
bool flag;
bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement();
if (isEmpty)
{
return;
}
while (true)
{
flag = false;
if (string.Compare(reader.Name, typeof(Foo1).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo1));
flag = true;
}
else if (string.Compare(reader.Name, typeof(Foo2).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo2));
flag = true;
}
if (flag)
this.Add((IFoo)serializer.Deserialize(reader));
else
break;
}
reader.ReadEndElement();
}

Generic class to Select element values in Xml to properties of a class

I have an xml like:
<?xml version="1.0" encoding="utf-8" ?>
<Property>
<Owner>
<FirstName>LeBron</FirstName>
<LastName>James</LastName>
</Owner>
<Seller>
<Name>LeBron</Name>
<Code>Seller01</Code>
<Branch>demoBranch</Branch>
</Seller>
<Equipments>
<Equipment>
<Name>Kodle</Name>
<CountryOfOrigin>Bryant</CountryOfOrigin>
</Equipment>
<Equipment>
<Name>Desktop</Name>
<CountryOfOrigin>Kryon</CountryOfOrigin>
</Equipment>
</Equipments>
</Property>
and classes:
public Property()
{
public Owner owner{get;set;}
public Seller seller{get;set;}
public equipment equipment{get;set;}
}
public Owner
{
public FirstName{get;set;}
public LastName{get;set;}
}
public Seller
{
public Name{get;set;}
public Code{get;set;}
public Branch{get;set;}
}
public Equipment
{
public Name{get;set;}
public CountryOfOrigin{get;set;}
}
Of course, we can load the class from the xml one by one,like:
XDocument xDoc = XDocument.Load(xmlFile);
var owner = from e in doc.Descendants("Owner")
select new Owner
{
FirstName = e.Element("FirstName").Value,
LastName = e.Element("LastName").Value
};
or deserialize the xml and get the sub class.
public Property LoadFromDocument(string fileUrl){
Property serializableObject = null;
using(TextReader textReader = textReader = new StreamReader(fileUrl)){
XmlSerializer xmlSerializer = new XmlSerializer(ObjectType);
serializableObject = xmlSerializer.Deserialize(textReader) as Property;
}
return serializableObject;
}
and then get owner by
Owner owner = LoadFromDocument(filrUrl).Owner;
but I want to create a generic class to do like bellow.
public class OXmlRepository<T>
{
public XDocument xDoc { get; set; }
protected XName ElementName { get; private set; }
protected OXmlRepository()
{
ElementName = typeof(T).Name.ToString();
}
public T GetObject()
{
//how can I get the class T instance with the values from Xml
}
}
public T GetObject()
{
var xs = new XmlSerializer(typeof(T));
using(var reader = xDoc.CreateReader())
{
return (T)xs.Deserialize(reader);
}
}

C# XML Serializer won't store an attribute

This is my first question on Stack Overflow. Apologies in advance if I don't do things quite right while I'm learning how things work here.
Here is my code :
public void TestSerialize()
{
ShoppingBag _shoppingBag = new ShoppingBag();
Fruits _fruits = new Fruits();
_fruits.testAttribute = "foo";
Fruit[] fruit = new Fruit[2];
fruit[0] = new Fruit("pineapple");
fruit[1]= new Fruit("kiwi");
_fruits.AddRange(fruit);
_shoppingBag.Items = _fruits;
Serialize<ShoppingBag>(_shoppingBag, #"C:\temp\shopping.xml");
}
public static void Serialize<T>(T objectToSerialize, string filePath) where T : class
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StreamWriter writer = new StreamWriter(filePath))
{
serializer.Serialize(writer, objectToSerialize);
}
}
[Serializable]
public class ShoppingBag
{
private Fruits _items;
public Fruits Items
{
get { return _items; }
set {_items = value; }
}
}
public class Fruits : List<Fruit>
{
public string testAttribute { get; set; }
}
[Serializable]
public class Fruit
{
public Fruit() { }
public Fruit(string value)
{
Name = value;
}
[XmlAttribute("name")]
public string Name { get; set; }
}
It produces this XML :
<?xml version="1.0" encoding="utf-8" ?>
<ShoppingBag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<Fruit name="pineapple" />
<Fruit name="kiwi" />
</Items>
</ShoppingBag>
I don't understand why I am not getting <Items testAttribute="foo">
Please can anyone tell me what I need to add to my code so that the Serializer will write this attribute out?
Thanks,
You need an intermediary class there:
class Program
{
static void Main()
{
var shoppingBag = new ShoppingBag
{
Items = new ShoppingBagItems
{
Fruits = new List<Fruit>(new[] {
new Fruit { Name = "pineapple" },
new Fruit { Name = "kiwi" },
}),
TestAttribute = "foo"
}
};
var serializer = new XmlSerializer(typeof(ShoppingBag));
serializer.Serialize(Console.Out, shoppingBag);
}
}
public class ShoppingBag
{
public ShoppingBagItems Items { get; set; }
}
public class ShoppingBagItems
{
[XmlElement("Fruit")]
public List<Fruit> Fruits { get; set; }
[XmlAttribute("testAttribute")]
public string TestAttribute { get; set; }
}
public class Fruit
{
[XmlAttribute("name")]
public string Name { get; set; }
}
Also note that you don't need to decorate your classes with the [Serializable] attribute as it is used only for binary serialization. Another remark is that you don't need to derive from List<T>, simply use it as a property.
Unfortunately, when serializing a collection the XmlSerializer doesn't take into account the extra properties of that collection. It only considers the members that implement ICollection<T>. If you want to serialize extra attributes, you need to wrap the collection in another class that is not a collection itself.

Categories