XML Deserialization Array - c#

I have a class. I want to deserialize XML file into this class.
public partial class Form1 : Form
{
public string xmlFile;
public Form1()
{
InitializeComponent();
xmlFile = "Sample.xml";
}
[Serializable]
public class Application
{
public string AppName;
public string UpdateDetail;
};
[Serializable]
public class Applications
{
[XmlElement]
public Application[] Application;
};
[Serializable]
public class Pmsp_Update
{
public string OldVersion;
public string NewVersion;
public Applications Applications;
};
private void btnRead_Click(object sender, EventArgs e)
{
XmlReader reader = XmlReader.Create(xmlFile);
XmlSerializer ser = new XmlSerializer(typeof(Pmsp_Update));
Pmsp_Update pu;
using (reader = XmlReader.Create(xmlFile))
{
pu = (Pmsp_Update)ser.Deserialize(reader);
}
}
}
And here is the XML:
<Pmsp_Update>
<OldVersion>v4.0.0</OldVersion>
<NewVersion>v4.0.1</NewVersion>
<Applications>
<Application>
<AppName>SampleApp</AppName>
<UpdateDetail>sample</UpdateDetail>
</Application>
</Applications>
</Pmsp_Update>
I want to ask about attributes. I only used [Serializable] attribute and I can get the class. I don't use any [XmlElement] attribute and this didn't cause any error except one.
If I use like this I can populate the array but:
[Serializable]
public class Applications
{
[XmlElement]
public Application[] Application;
};
If I use like this I can't populate the array:
[Serializable]
public class Applications
{
public Application[] Application;
};
So, my question is this: I am not using [XmlElement] for fields and everything works well but I can't populate the array. Why couldn't I populate the array when I don't use [XmlElement] although I can populate the fields?

The easiest way to check what's going on is to create object of your class and serialize it to XML. This is what I get:
Without [XmlElement]
<?xml version="1.0" encoding="utf-16"?>
<Pmsp_Update xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OldVersion>v4.0.0</OldVersion>
<NewVersion>v.4.0.1</NewVersion>
<Applications>
<Application>
<Application>
<AppName>Test</AppName>
<UpdateDetail>test</UpdateDetail>
</Application>
</Application>
</Applications>
</Pmsp_Update>
With [XmlElement]
<?xml version="1.0" encoding="utf-16"?>
<Pmsp_Update xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OldVersion>v4.0.0</OldVersion>
<NewVersion>v.4.0.1</NewVersion>
<Applications>
<Application>
<AppName>Test</AppName>
<UpdateDetail>test</UpdateDetail>
</Application>
</Applications>
</Pmsp_Update>
As you can see, only the second one matches your input XML, and that's why you cannot deserialize yout XML without [XmlElement] attribute - because your XML document does not match your class structure.
Empty XmlElement on top of array property prevent serializer from expecting additional element to handle array elements. That behavior is described on MSDN: XmlElementAttribute Class
If you apply the XmlElementAttribute to a field or property that
returns an array, the items in the array are encoded as a sequence of
XML elements.
In contrast if an XmlElementAttribute is not applied to
such a field or property, the items in the array are encoded as a
sequence of elements, nested under an element named after the field or
property. (Use the XmlArrayAttribute and XmlArrayItemAttribute
attributes to control how an array is serialized.)
You can achieve the same by changing your class structure to
public class Application
{
public string AppName;
public string UpdateDetail;
};
public class Pmsp_Update
{
public string OldVersion;
public string NewVersion;
public Application[] Applications;
};
<Applications> element will be added automatically for array property, so you don't need Applications class.

Related

How to serialize an object containing an XML property

Note this is .NET 4.8
I have created this sample code to illustrate the problem
[XmlRoot(ElementName = "RESULT", Namespace = "", IsNullable = false)]
public class Result
{
public string Message { get; set; }
public XElement Stuff { get; set; }
public override string ToString()
{
var ser = new XmlSerializer(GetType());
using (var stream = new StringWriter())
{
ser.Serialize(stream, this);
return stream.ToString();
}
}
}
I will have some XML already that looks like this
<FOO>
<BAR>Hello World</BAR>
<BAR2>Hello World</BAR2>
<BAR3>Hello World</BAR3>
</FOO>
This is assigned to the XElement Stuff property and when an instance of Result is then serialized, you get this XML:
<?xml version="1.0" encoding="utf-16"?>
<RESULT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>Hello World</Message>
<Stuff>
<FOO>
<BAR>Hello World</BAR>
<BAR2>Hello World</BAR2>
<BAR3>Hello World</BAR3>
</FOO>
</Stuff>
</RESULT>
Question: Is there any way to get this result instead?
<?xml version="1.0" encoding="utf-16"?>
<RESULT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message>Hello World</Message>
<FOO>
<BAR>Hello World</BAR>
<BAR2>Hello World</BAR2>
<BAR3>Hello World</BAR3>
</FOO>
</RESULT>
Note: FOO could be Stuff - I don't care (because I know how to change that name) I just don't want two levels of nested XML for that property in the serialised XML
You can play with the sample code here
If you're happy for the root name to be hard-coded, then you can write a wrapper type for the elements and implement IXmlSerializable within.
This is likely preferable to implementing in Result, as I imagine the real type would have more than 2 properties.
A quick and dirty example - I'll leave implementing ReadXml to you (if you need it):
public class ElementsWrapper : IXmlSerializable
{
public XElement[] Elements { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
}
public void WriteXml(XmlWriter writer)
{
foreach (var element in Elements)
{
element.WriteTo(writer);
}
}
}
And change your property in Result to:
public ElementsWrapper FOO { get; set; }
When used, the serialiser for Result will write <FOO>, then delegate to the custom serialisation, and then write </FOO>.
You can see an updated fiddle here.

How to use array with XmlSerializer?

I'm making some test to use it.
I have the following xml:
<?xml version="1.0"?>
<test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ma>233</ma>
<ma>2333</ma>
</test>
I have this class to deserialize it:
[Serializable]
public class test
{
public string ma { get; set; }
}
It does contains the first element. Now I want both so I try setting an array
[Serializable]
public class test
{
public string[] ma { get; set; }
}
However setting an array I have now 0 result in ma variable, while I at least have the first one when it is not an array.
I found this answer Using XmlSerializer with an array in the root element, but he used another logic... I'd like to keep using [Serializable]
You have to indicate that the array doesn't have a separate xml element to wrap its items, but that the array items appear directly under the <test> element:
public class test
{
[XmlElement]
public string[] ma { get; set; }
}
PS. sometimes it's hard to get the mapping right - I usually fill in a class with test data and serilalize it, examining what XmlSerializer makes of that usually clears up what's going on.
The answer you found provides the information you need.
[Serializable] doesn't help you because it isn't used by XmlSerializer, see Why doesn't the XmlSerializer need the type to be marked [Serializable]?

XML Serializing a List<Class>

I'm having issues deserializing XML which I can't quite figure out. Mainly, I'm trying to avoid having to nest classes when they're inside a list but not succeeding. For example:
[XmlRoot]
[Serializable]
public class Foo
{
[XmlElement("Bar")]
public BarElement Bar = new BarElement();
public class BarElement
{
[XmlElement("MoreBars")]
public List<MoreElement> MoreBars = new List<MoreElement>();
}
[XmlRoot("More")]
[Serializable]
public class MoreElement
{
[XmlAttribute("Attribute")]
public string Attribute { get; set; }
[XmlText]
public string Value { get; set; }
}
}
Corresponds to:
<Foo>
<Bar>
<MoreBars>
<More Attribute=""></More>
<More Attribute=""></More>
<More Attribute=""></More>
<More Attribute=""></More>
</MoreBars>
</Bar>
</Foo>
This almost works... but not quite. By adding XmlRoot to MoreElement, I'm trying to avoid the necessity of having to create a new class named "MoreBarsElement" that contains only a List of MoreElements, since it's already quite a chore to access "Foo.Bar.MoreBars.Value". Is this possible? If so, how would I do it?
Answering my own question - looks like I needed
[XmlArray("MoreBars"), XmlArrayItem(typeof(MoreElement), ElementName = "More")]
On the array item declaration. It works now, hurray!

Serializing class, "move" attribute to other element

I'm trying to constrol where a class property is rendered when the class is serialized: I need the property to appear as an attribute on a specific element:
namespace ConsoleApplication6
{
public class Program
{
static void Main(string[] args)
{
var myClass = new MyClass();
myClass.MyList.Add(new Item() { ID = 1 });
myClass.MyList.Add(new Item() { ID = 2 });
myClass.Xxx = "Hello World!";
var sx = new XmlSerializer(myClass.GetType());
sx.Serialize(Console.Out, myClass);
}
public class MyClass
{
public MyClass()
{
MyList = new List<Item>();
}
public List<Item> MyList { get; set; }
[XmlAttributeAttribute(AttributeName = "x")]
public string Xxx { get; set; }
}
public class Item
{
public int ID { get; set; }
}
}
}
This serializes quite nicely into this:
<?xml version="1.0" encoding="ibm850"?>
<MyClass xmlns:xsi=" ... " xmlns:xsd=" ... " x="Hello World!">
<MyList>
<Item>
<ID>1</ID>
</Item>
<Item>
<ID>2</ID>
</Item>
</MyList>
</MyClass>
BUT: My problem is, I need the property Xxx to be rendered as an attribute on the <MyList> element rather than the <MyClass> root element, like this:
...
<MyList x="Hello World!">
...
I'm GUESSING this should be possible using XmlSerialization attributes on the class/properties, but I can't figure it out. I even tried creating a subclass of List adding the property Xxx to that, but the .NET XML serialization ignores the extra properties, and the XML output is just like the List<..> is normally serialized.
Update: Here's the code where I try to create a "custom list", that inherits from List<Item> and adds an extra property:
public class Program
{
static void Main(string[] args)
{
var myClass = new MyClass();
myClass.MyList.Add(new Item() { ID = 1 });
myClass.MyList.Add(new Item() { ID = 2 });
myClass.MyList.Xxx = "Hello World!";
var sx = new XmlSerializer(myClass.GetType());
sx.Serialize(Console.Out, myClass);
}
public class MyClass
{
public MyClass()
{
MyList = new CustomList();
}
public CustomList MyList { get; set; }
}
public class Item
{
public int ID { get; set; }
}
public class CustomList : List<Item>
{
[XmlAttributeAttribute(AttributeName = "x")]
public string Xxx { get; set; }
}
}
The output xml looks like this:
<?xml version="1.0" encoding="ibm850"?>
<MyClass xmlns:xsi=" ... " xmlns:xsd=" ... ">
<MyList>
<Item>
<ID>1</ID>
</Item>
<Item>
<ID>2</ID>
</Item>
</MyList>
</MyClass>
Notice how the Xxx property is not represented in the xml...
I think for that level of control you will need to look at using the IXmlSerializable interface. I don't think using attributes will work here.
According to this MSDN discussion:
XmlSerializer does not serialize any members if a collection. Only
collection items get serialized. This is by design, basically a
decision was made to handle collections as arrays not as classes with
multiple properties, so collections should look like arrays on the
wire, therefore they do not have any members other then collection
items, and can be “flattened” by adding the [XmlElement] to the member
of the ICollection type.
So apparently the flaw you describe is by design. Unless you decide to run the resulting XML through some transforms, I'm not sure how you plan to get this to work using the XML serialization attributes.
This post provides some additional information, including a few options:
When a class is inherited from List<>, XmlSerializer doesn't serialize other attributes
Summary:
IXmlSerializable (as mentioned by Kai)
DataContractSerializer
Create a new class where X (your attribute) is a property and provide an additional property that is a list (so instead of subclassing list, just create a wrapper class). For example:
public class MyListWrapper<T>
{
public MyListWrapper()
{
Data = new List<T>();
}
[XmlAttribute(AttributeName="x")]
public string Xxx { get; set; }
[XmlElement]
public List<T> Data { get; set; }
}
Note that this will output Items as "Data" elements. If you're willing to drop the generic type parameter on Data and make it List(Item) then you can get items.
Hope that helps!

C# XML Serialization of Attribute

I have some XML which I would like to serialize into a class.
<MasterData>
<Data>
<SomeInnerData>
some inner data
</SomeInnerData>
</Data>
</MasterData>
<MoreData>
<SubMoreData>moredate</SubMoreData>
</MoreDate>
and
[System.SerializableAttribute()]
public class MasterData
{
public string SomeInnnerData {get;set;}
public string SubMoreDate {get;set;}
}
How do I set the string member variables to serialize the appropriate data in the XML? My issue arises in that the element is not a child of the MasterData element.
The simplest way is to work backwards, get your class setup to serialize into the format you want, so that you can deserialize into it with ease.
Note: Your xml didn't validate, so I changed it to this for an example
<MasterData>
<Data>
<SomeInnerData>some inner data</SomeInnerData>
</Data>
<MoreData>
<SubMoreData>moredate</SubMoreData>
</MoreDate>
</MasterData>
Currently, your problem is that you have Data and MoreData elements that don't map to anything
You'd need to create classes like
public class MasterData {
public Data Data {get; set;}
public MoreData Data {get; set;}
}
public class Data {
public string SomeInnerData {get; set;}
}
public class MoreData {
public string SubMoreData {get; set;}
}
You can have your properties named other things, and use the [XmlElement(ElementName="SubMoreData")] to map the property, to the correct Element.
Or, you could implement the IXmlSerializable interface, and write custom serialization code in a single class to map your class to xml however you want
The XML example you provided is not valid XML thus you will not be able to directly serialize it. You need a root node for it work this way.
Such that:
<AllData>
<MasterData>
<Data>
<SomeInnerData>
some inner data
</SomeInnerData>
</Data>
</MasterData>
<MoreData>
<SubMoreData>moredate</SubMoreData>
</MoreDate>
<AllData>
[System.SerializableAttribute()]
public class AllData
{
public MasterData MasterData {get;set;}
public MoreData MoreData {get;set;}
}
[System.SerializableAttribute()]
public class Data
{
public string SomeInnnerData {get;set;}
}
[System.SerializableAttribute()]
public class MasterData
{
public string SomeInnnerData {get;set;}
}
[System.SerializableAttribute()]
public class MasterData
{
public Data Data {get;set;}
}
[System.SerializableAttribute()]
public class MoreData
{
public string SubMoreDate {get;set;}
}
Implement ixmlserializable for custom serialization
With the normal .NET XMLSerializer class, public properties are serialized by default. You have to explicitly attribute properties not to be serialized:
[System.Xml.Serialization.XmlIgnoreAttribute]
Here's the documentation: XmlSerializer
...
Now that you've revised your question, the answer is that you will no longer be able to use the XMLSerializer class. (Or you will have to have some intermediary class which matches the structure of your XML, to facilitate the serialization and deserialization.) If you have a very specific XML structure that you want transform arbitrarily, use an XmlReader.
How about using XSL to transform from/to the XML format that your C# class directly serializes to?

Categories