Deserialize XML with multiple elements into single C# object - c#

I need to deserialize an XML document that looks like this:
<Root>
<Items>
<Item>
<ItemHeader Attr1="A" Attr2="B" Attr3="C" />
<ItemDetails Attr4="D" Attr5="E" />
</Item>
...
</Items>
</Root>
Into a class that looks like this:
[Serializable, XmlRoot("Item")]
Public class MyItem
{
[XmlAttribute("Attr1")]
public string Attr1 { get; set; }
[XmlAttribute("Attr5")]
public string Attr5 { get; set; }
}
And I am using the following code to perform the deserialization:
XDocument doc;
XElement rootElem = doc.Element("Root");
foreach (XElement xe in rootElem.Descendants("Item"))
{
MyItem item = new MyItem();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyItem));
XmlReader xRdr = xe.CreateReader();
item = (MyItem)xmlSerializer.Deserialize(xRdr);
}
However, none of the elements are copied into the object instance.
Is this doable? Do I need to deserialize each sub Element?
Thanks

I'm not sure there's a way to do that using the default XML serializer via attributes, without doing the whole class structure to match your XML - so ItemHeader and ItemDetails would need their own class.
You can implement the IXmlSerializable interface though, so you can completely customize - if you must keep the structure of MyItem as it is.
static void Main(string[] args)
{
XmlSerializer myItemSerializer = new XmlSerializer(typeof(MyItem));
var xmlDoc = XDocument.Parse(#"<Item>
<ItemHeader Attr1=""A"" Attr2=""B"" Attr3=""C"" />
<ItemDetails Attr4=""D"" Attr5=""E"" />
</Item>");
using (var reader = xmlDoc.CreateReader())
{
MyItem myItem = (MyItem)myItemSerializer.Deserialize(reader);
}
Console.Read();
}
[Serializable, XmlRoot("Item")]
public class MyItem : IXmlSerializable
{
[XmlAttribute("Attr1")]
public string Attr1 { get; set; }
[XmlAttribute("Attr5")]
public string Attr5 { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement("Item");
do
{
switch (reader.Name)
{
case "ItemHeader":
Attr1 = reader.GetAttribute("Attr1");
reader.Read();
break;
case "ItemDetails":
Attr5 = reader.GetAttribute("Attr5");
reader.Read();
break;
default:
throw new XmlException(String.Format("{0} was not expected", reader.Name));
}
} while (reader.Name != "Item");
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("ItemHeader");
writer.WriteAttributeString("Attr1", Attr1);
writer.WriteEndElement();
writer.WriteStartElement("ItemDetails");
writer.WriteAttributeString("Attr5", Attr5);
writer.WriteEndElement();
}
}

The issue is the xml data you are receiving does not match the serialization attributes of MyItem, schemas differ. There are multiple ways around this but I guess the quickest and dirtiest solution would be to extract the part that interests you:
XDocument doc = XDocument.Load (#"Your.xml");
XElement rootElem = doc.Element("Root");
XElement itemsElem = rootElem.Element("Items");
foreach (XElement xe in itemsElem.Elements("Item"))
{
MyItem item = new MyItem()
{
Attr1 = xe.Element("ItemHeader").Attribute("Attr1").Value,
Attr5 = xe.Element("ItemDetails").Attribute("Attr5").Value
};
}

Related

Different element serialization styles for empty strings [duplicate]

When I serialize the value : If there is no value present in for data then it's coming like below format.
<Note>
<Type>Acknowledged by PPS</Type>
<Data />
</Note>
But what I want xml data in below format:
<Note>
<Type>Acknowledged by PPS</Type>
<Data></Data>
</Note>
Code For this i have written :
[Serializable]
public class Notes
{
[XmlElement("Type")]
public string typeName { get; set; }
[XmlElement("Data")]
public string dataValue { get; set; }
}
I am not able to figure out what to do for achieve data in below format if data has n't assign any value.
<Note>
<Type>Acknowledged by PPS</Type>
<Data></Data>
</Note>
You can do this by creating your own XmlTextWriter to pass into the serialization process.
public class MyXmlTextWriter : XmlTextWriter
{
public MyXmlTextWriter(Stream stream) : base(stream, Encoding.UTF8)
{
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
You can test the result using:
class Program
{
static void Main(string[] args)
{
using (var stream = new MemoryStream())
{
var serializer = new XmlSerializer(typeof(Notes));
var writer = new MyXmlTextWriter(stream);
serializer.Serialize(writer, new Notes() { typeName = "Acknowledged by PPS", dataValue="" });
var result = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(result);
}
Console.ReadKey();
}
If you saved your string somewhere (e.g a file) you can use this simple Regex.Replace:
var replaced = Regex.Replace(File.ReadAllText(name), #"<([^<>/]+)\/>", (m) => $"<{m.Groups[1].Value.Trim()}></{m.Groups[1].Value.Trim()}>");
File.WriteAllText(name, replaced);
IMO it's not possibe to generate your desired XML using Serialization. But, you can use LINQ to XML to generate the desired schema like this -
XDocument xDocument = new XDocument();
XElement rootNode = new XElement(typeof(Notes).Name);
foreach (var property in typeof(Notes).GetProperties())
{
if (property.GetValue(a, null) == null)
{
property.SetValue(a, string.Empty, null);
}
XElement childNode = new XElement(property.Name, property.GetValue(a, null));
rootNode.Add(childNode);
}
xDocument.Add(rootNode);
XmlWriterSettings xws = new XmlWriterSettings() { Indent=true };
using (XmlWriter writer = XmlWriter.Create("D:\\Sample.xml", xws))
{
xDocument.Save(writer);
}
Main catch is in case your value is null, you should set it to empty string. It will force the closing tag to be generated. In case value is null closing tag is not created.
Kludge time - see Generate System.Xml.XmlDocument.OuterXml() output thats valid in HTML
Basically after XML doc has been generated go through each node, adding an empty text node if no children
// Call with
addSpaceToEmptyNodes(xmlDoc.FirstChild);
private void addSpaceToEmptyNodes(XmlNode node)
{
if (node.HasChildNodes)
{
foreach (XmlNode child in node.ChildNodes)
addSpaceToEmptyNodes(child);
}
else
node.AppendChild(node.OwnerDocument.CreateTextNode(""))
}
(Yes I know you shouldn't have to do this - but if your sending the XML to some other system that you can't easily fix then have to be pragmatic about things)
You can add a dummy field to prevent the self-closing element.
[XmlText]
public string datavalue= " ";
Or if you want the code for your class then Your class should be like this.
public class Notes
{
[XmlElement("Type")]
public string typeName { get; set; }
[XmlElement("Data")]
private string _dataValue;
public string dataValue {
get {
if(string.IsNullOrEmpty(_dataValue))
return " ";
else
return _dataValue;
}
set {
_dataValue = value;
}
}
}
In principal, armen.shimoon's answer worked for me. But if you want your XML output pretty printed without having to use XmlWriterSettings and an additional Stream object (as stated in the comments), you can simply set the Formatting in the constructor of your XmlTextWriter class.
public MyXmlTextWriter(string filename) : base(filename, Encoding.UTF8)
{
this.Formatting = Formatting.Indented;
}
(Would have posted this as a comment but am not allowed yet ;-))
Effectively the same as Ryan's solution which uses the standard XmlWriter (i.e. there's no need for a derived XmlTextWriter class), but written using linq to xml (XDocument)..
private static void AssignEmptyElements(this XNode node)
{
if (node is XElement e)
{
e.Nodes().ToList().ForEach(AssignEmptyElements);
if (e.IsEmpty)
e.Value = string.Empty;
}
}
usage..
AssignEmptyElements(document.FirstNode);

C# serialize object to XML with element containing XML text without escaping

I searched and tried some attributes but none of them worked for my below scenario:
public class ObjSer
{
[XmlElement("Name")]
public string Name
{
get; set;
}
}
//Code to serialize
var obj = new ObjSer();
obj.Name = "<tag1>Value</tag1>";
var stringwriter = new System.IO.StringWriter();
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(stringwriter, obj);
Output would be as follows:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
But I need output as:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
Scenario 2: In some cases I need to set:
obj.Name = "Value";
Is there any attribute or method I can override to make it possible?
You can't with default serializer. XmlSerializer does encoding of all values during serialization.
If you want your member to hold xml value, it must be XmlElement. Here is how you can accomplish it
public class ObjSer
{
[XmlElement("Name")]
public XmlElement Name
{
get; set;
}
}
var obj = new ObjSer();
// <-- load xml
var doc = new XmlDocument();
doc.LoadXml("<tag1>Value</tag1>");
obj.Name = doc.DocumentElement;
// --> assign the element
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
Output:
<?xml version="1.0" encoding="IBM437"?>
<ObjSer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>
<tag1>Value</tag1>
</Name>
</ObjSer>
UPDATE:
In case if you want to use XElement instead of XmlElement, here is sample on how to do it
public class ObjSer
{
[XmlElement("Name")]
public XElement Name
{
get; set;
}
}
static void Main(string[] args)
{
//Code to serialize
var obj = new ObjSer();
obj.Name = XDocument.Parse("<tag1>Value</tag1>").Document.FirstNode as XElement;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
}
No, you can't. It is the natural and usual behaviour of the xml serializer. The serializer doesn't have to handle within the XML strings. So, it escapes the xml as expected.
You should decode the escaped string again while deserializing it.
If you want to add dynamically elements in the XML I suggest you to use Linq to XML and you can create tag1 or another kind of elements easily.
I would suggest serializing to an XDocument then converting that to a string and manually unescaping the desired part and writing it to a file. I would say this would give you the least headache it shouldn't be more than a couple lines of code. If you need it I can provide some code example if needed.
I found one more way of changing the type
public class NameNode
{
public string tag1
{
get; set;
}
[XmlText]
public string Value
{
get; set;
}
}
public class ObjSer
{
[XmlElement("Name")]
public NameNode Name
{
get; set;
}
}
To set value of Name:
var obj = new ObjSer();
var valueToSet = "<tag1>Value</tag1>";
//or var valueToSet = "Value";
//With XML tag:
if (valueToSet.Contains("</"))
{
var doc = new XmlDocument();
doc.LoadXml(valueToSet);
obj.Name.tag1 = doc.InnerText;
}
else //Without XML Tags
{
obj.Name.Value = senderRecipient.Name;
}
This solution will work in both cases, but has limitation. It will work only for predefined tags (ex. tag1)

How to deserialize xml to derived classes base on element's value?

For example I have an xml:
<MyFruit>
<Fruit>
<Name>Apple</Name>
<Size>Big</Size>
</Fruit>
<Fruit>
<Name>Orange</Name>
<Price>10.00</Price>
</Fruit>
</MyFruit>
You may notice that the fruit nodes contain different elements, that's my hurt:(
Then I defined following classes in order to hold the deserialized object:
public class MyFruit
{
public List<Fruit> Fruits { get; set; }
}
public abstract class Fruit
{
public string Name { get; set; }
}
public class Apple : Fruit
{
public string Size { get; set; }
}
public class Orange : Fruit
{
public float Price { get; set; }
}
It didn't work.
I also tried:
Adding [XmlInclude(typeof (Apple))] and [XmlInclude(typeof (Orange))] attributes to the fruit base class to specify the concrete derived classes
Adding [XmlElement(typeof (Apple))] and [XmlElement(typeof (Orange)) attributes to the Fruits property of the MyFruit class
Neither of them works.
So I wonder is there a way that can control the deserialization process base on element's value(if the name is Apple, deserialize to Apple class, Orange to Orange class...), or maybe there are some better ways?
UPDATE
I wrote an extension method to deserialize xml:
public static T Deserialize<T>(this string xml)
{
if (string.IsNullOrEmpty(xml))
{
return default(T);
}
try
{
var xmlserializer = new XmlSerializer(typeof(T));
var stringReader = new StringReader(xml);
using (var reader = XmlReader.Create(stringReader))
{
return (T) xmlserializer.Deserialize(reader);
}
}
catch (Exception ex)
{
throw new Exception("反序列化发生错误", ex);
}
}
One approach is simply to transform the input xml via the XslCompiledTransform class into a format that can be easily de-serialized into the desired object structure. The following example demonstrates the concept:
// XML Deserialization helper.
class XmlSerializationHelper
{
// Transform the input xml to the desired format needed for de-serialization.
private static string TransformXml(string xmlString)
{
// XSL transformation script.
string xsl = #"<xsl:stylesheet xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"" version=""1.0"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<xsl:template match=""MyFruit"">
<xsl:element name=""{local-name()}"">
<Fruits>
<xsl:for-each select=""Fruit"">
<xsl:element name=""Fruit"">
<xsl:attribute name=""xsi:type""><xsl:value-of select=""Name""/></xsl:attribute>
<xsl:copy-of select=""./node()""/>
</xsl:element>
</xsl:for-each>
</Fruits>
</xsl:element>
</xsl:template>
</xsl:stylesheet>";
// Load input xml as XmlDocument
XmlDocument sourceXml = new XmlDocument();
sourceXml.LoadXml(xmlString);
// Create XSL transformation.
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(new XmlTextReader(new StringReader(xsl)));
// Apply transformation to input xml and write result out to target xml doc.
XmlDocument targetXml = new XmlDocument(sourceXml.CreateNavigator().NameTable);
using (XmlWriter writer = targetXml.CreateNavigator().AppendChild())
{
transform.Transform(sourceXml, writer);
}
// Return transformed xml string.
return targetXml.InnerXml;
}
public static T DeSerialize<T>(string inputXml)
{
T instance = default(T);
if (string.IsNullOrEmpty(inputXml))
return instance;
try
{
string xml = TransformXml(inputXml); // Transform the input xml to the desired xml format needed to de-serialize objects.
string attributeXml = string.Empty;
using (StringReader reader = new StringReader(xml))
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (XmlReader xmlReader = new XmlTextReader(reader))
{
instance = (T)serializer.Deserialize(xmlReader);
xmlReader.Close();
}
reader.Close();
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return instance;
}
}
The helper class can now be used as follow:
string inputXml = #"<MyFruit>
<Fruit>
<Name>Apple</Name>
<Size>Big</Size>
</Fruit>
<Fruit>
<Name>Orange</Name>
<Price>10.00</Price>
</Fruit>
</MyFruit>";
MyFruit fruits = XmlSerializationHelper.DeSerialize<MyFruit>(inputXml);
This one will work:
[XmlRoot("MyFruit")]
public class MyFruit : List<Fruit>
{
}
public class Fruit
{
public string Name { get; set; }
public string Size { get; set; }
public float Price { get; set; }
}
Use Deserialize from #WAKU
You can use a loop and XmlParser. Switch on the 'Name' property to parse the class specific tags. Example:
Fruit fruit;
List<Fruit> fruits;
while(true)
{
// ...
xmlReader.Read();
xmlReader.ReadStartElement("Fruit");
xmlReader.ReadStartElement("Name");
name = xmlReader.ReadString();
xmlReader.ReadEndElement();
switch(name)
{
case "apple":
fruit = new Apple();
try
{
xmlReader.ReadStartElement("weight");
(fruit as Apple).weight = Integer.Parse(xmlReader.ReadString());
xmlReader.ReadEndElement();
}catch(){}
//.....
break;
case "orange":
fruit = new Orange;
try
{
xmlReader.ReadStartElement("color");
(fruit as Orange).color = xmlReader.ReadString();
xmlReader.ReadEndElement();
}catch(){}
//.....
break;
}
xmlReader.ReadEndElement();
// ...
}

How to serialize an ICollection<T> that also has read/write properties to XML

I have class that implement list of custom class. That class also has two properties. But when I serialize that class, XML contains only array of my custom classes but don't contains another two properties.
Here is the class:
public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
public long KomSifra { get; set; }
public Guid KomId { get; set; }
IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
{
var sqlRow = new SqlDataRecord(
new SqlMetaData("rb", SqlDbType.Int),
new SqlMetaData("RobaSifra", SqlDbType.NVarChar, 50),
new SqlMetaData("RobaNaziv", SqlDbType.NVarChar, 100)
);
foreach (PorudzbenicaStavka por in this)
{
sqlRow.SetInt32(0, por.rb);
sqlRow.SetString(1, por.RobaSifra);
sqlRow.SetString(2, por.RobaNaziv);
yield return sqlRow;
}
}
}
and code that I use to serialize it:
XmlSerializer serializer = new XmlSerializer(typeof(Porudzbina));
using (TextWriter writer = new StreamWriter(#"C:\Xmle.xml"))
{
serializer.Serialize(writer, por);
}
and this is XML that I got:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPorudzbenicaStavka xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PorudzbenicaStavka>
<rb>1</rb>
<RobaSifra>3702</RobaSifra>
<RobaNaziv>Foullon mlecna cokolada 33% Ecuador 100g</RobaNaziv>
</PorudzbenicaStavka>
<PorudzbenicaStavka>
<rb>2</rb>
<RobaSifra>1182</RobaSifra>
<RobaNaziv>IL Capitano zelena maslina sa paprikom 720g</RobaNaziv>
</PorudzbenicaStavka>
<PorudzbenicaStavka>
<rb>3</rb>
<RobaSifra>1120</RobaSifra>
<RobaNaziv>Kaiser tuna steak sa papricicom u ulju 170g.</RobaNaziv>
</PorudzbenicaStavka>
</ArrayOfPorudzbenicaStavka>
I want my xml contains two properties together with a array of custom class, that I could deserialize it into its original state...
The reason that your properties are not deserialized is explained in the documentation section Serializing a Class that Implements the ICollection Interface:
You can create your own collection classes by implementing the ICollection interface, and use the XmlSerializer to serialize instances of these classes. Note that when a class implements the ICollection interface, only the collection contained by the class is serialized. Any public properties or fields added to the class will not be serialized.
So, that's that.
You might consider changing your design so your classes do not have properties. For some reasons to make this change, see Why not inherit from List?.
If you nevertheless choose to have a collection with serializable properties, you're going to need to manually implement IXmlSerializable. This is burdensome, since you need to handle many "edge" cases including empty elements, unexpected elements, comments, and presence or absence of whitespace, all of which can throw off your ReadXml() method. For some background, see How to Implement IXmlSerializable Correctly.
First, create a base class for generic lists with serializable properties:
public class XmlSerializableList<T> : List<T>, IXmlSerializable where T : new()
{
public XmlSerializableList() : base() { }
public XmlSerializableList(IEnumerable<T> collection) : base(collection) { }
public XmlSerializableList(int capacity) : base(capacity) { }
#region IXmlSerializable Members
const string CollectionItemsName = "Items";
const string CollectionPropertiesName = "Properties";
void IXmlSerializable.WriteXml(XmlWriter writer)
{
// Do not write the wrapper element.
// Serialize the collection.
WriteCollectionElements(writer);
// Serialize custom properties.
writer.WriteStartElement(CollectionPropertiesName);
WriteCustomElements(writer);
writer.WriteEndElement();
// Do not end the wrapper element.
}
private void WriteCollectionElements(XmlWriter writer)
{
if (Count < 1)
return;
// Serialize the collection.
writer.WriteStartElement(CollectionItemsName);
var serializer = new XmlSerializer(typeof(T));
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
foreach (var item in this)
{
serializer.Serialize(writer, item, ns);
}
writer.WriteEndElement();
}
/// <summary>
/// Write ALL custom elements to the XmlReader
/// </summary>
/// <param name="writer"></param>
protected virtual void WriteCustomElements(XmlWriter writer)
{
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the wrapper element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType != XmlNodeType.Element)
// Comment, whitespace
reader.Read();
else if (reader.IsEmptyElement)
reader.Read();
else if (reader.Name == CollectionItemsName)
ReadCollectionElements(reader);
else if (reader.Name == CollectionPropertiesName)
ReadCustomElements(reader);
else
// Unknown element, skip it.
reader.Skip();
}
// Move past the end of the wrapper element
reader.ReadEndElement();
}
void ReadCustomElements(XmlReader reader)
{
reader.ReadStartElement(); // Advance to the first sub element of the collection element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element)
{
using (var subReader = reader.ReadSubtree())
{
while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
if (!subReader.Read())
break;
ReadCustomElement(subReader);
}
}
reader.Read();
}
// Move past the end of the properties element
reader.Read();
}
void ReadCollectionElements(XmlReader reader)
{
var serializer = new XmlSerializer(typeof(T));
reader.ReadStartElement(); // Advance to the first sub element of the collection element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element)
{
using (var subReader = reader.ReadSubtree())
{
while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
if (!subReader.Read())
break;
var item = (T)serializer.Deserialize(subReader);
Add(item);
}
}
reader.Read();
}
// Move past the end of the collection element
reader.Read();
}
/// <summary>
/// Read ONE custom element from the XmlReader
/// </summary>
/// <param name="reader"></param>
protected virtual void ReadCustomElement(XmlReader reader)
{
}
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
#endregion
}
To use this class, you will need to override ReadCustomElement(XmlReader reader), which reads a single custom property, and WriteCustomElements(XmlWriter writer), which writes all custom properties. (Note the asymmetry, it makes implementing class hierarchies a little easier.) Then create your Porudzbina class as follows:
public class Porudzbina : XmlSerializableList<PorudzbenicaStavka>
{
public long KomSifra { get; set; }
public Guid KomId { get; set; }
const string KomSifraName = "KomSifra";
const string KomIdName = "KomId";
protected override void WriteCustomElements(XmlWriter writer)
{
writer.WriteElementString(KomSifraName, XmlConvert.ToString(KomSifra));
writer.WriteElementString(KomIdName, XmlConvert.ToString(KomId));
base.WriteCustomElements(writer);
}
protected override void ReadCustomElement(XmlReader reader)
{
if (reader.Name == KomSifraName)
{
KomSifra = reader.ReadElementContentAsLong();
}
else if (reader.Name == KomIdName)
{
var s = reader.ReadElementContentAsString();
KomId = XmlConvert.ToGuid(s);
}
else
{
base.ReadCustomElement(reader);
}
}
}
This will create XML that looks like:
<Porudzbina>
<Items>
<PorudzbenicaStavka>
<!-- contents of first PorudzbenicaStavka -->
</PorudzbenicaStavka>
<!-- Additional PorudzbenicaStavka -->
</Items>
<Properties>
<KomSifra>101</KomSifra>
<KomId>bb23a3b8-23d3-4edd-848b-d7621e6ed2c0</KomId>
</Properties>
</Porudzbina>
I've uploaded my serialization library to Github, where such issues are handled.
Atlas Xml Serializer
I'm assuming that you have below data classes. I've just added [XmlElement] attribute over properties to force them serialize into xml elements.
public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
[XmlElement]
public long KomSifra { get; set; }
[XmlElement]
public Guid KomId { get; set; }
IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
{
var sqlRow = new SqlDataRecord(
new SqlMetaData("rb", SqlDbType.Int),
new SqlMetaData("RobaSifra", SqlDbType.NVarChar, 50),
new SqlMetaData("RobaNaziv", SqlDbType.NVarChar, 100)
);
foreach (PorudzbenicaStavka por in this)
{
sqlRow.SetInt32(0, por.rb);
sqlRow.SetString(1, por.RobaSifra);
sqlRow.SetString(2, por.RobaNaziv);
yield return sqlRow;
}
}
}
public class PorudzbenicaStavka
{
[XmlElement]
public int rb { get; set; }
[XmlElement]
public string RobaSifra { get; set; }
[XmlElement]
public string RobaNaziv { get; set; }
}
And here's the instance:
var o = new Porudzbina
{
new PorudzbenicaStavka { rb=1, RobaSifra="3702", RobaNaziv="Foullon mlecna cokolada 33% Ecuador 100g" },
new PorudzbenicaStavka { rb=2, RobaSifra="1182", RobaNaziv="IL Capitano zelena maslina sa paprikom 720g" },
new PorudzbenicaStavka { rb=3, RobaSifra="1120", RobaNaziv="Kaiser tuna steak sa papricicom u ulju 170g." },
};
o.KomId = new Guid("{EC63AEC3-1512-451F-B967-836DD0E9820A}");
o.KomSifra = 999999;
And here's how atlas xml serialization library does the job:
var serialized = Atlas.Xml.Serializer.Serialize(o, true);
var deserialized = Atlas.Xml.Serializer.Deserialize<Porudzbina>(serialized);
Xml would look like this:
<Porudzbina>
<KomSifra>999999</KomSifra>
<KomId>ec63aec3-1512-451f-b967-836dd0e9820a</KomId>
<item>
<rb>1</rb>
<RobaSifra>3702</RobaSifra>
<RobaNaziv>Foullon mlecna cokolada 33% Ecuador 100g</RobaNaziv>
</item>
<item>
<rb>2</rb>
<RobaSifra>1182</RobaSifra>
<RobaNaziv>IL Capitano zelena maslina sa paprikom 720g</RobaNaziv>
</item>
<item>
<rb>3</rb>
<RobaSifra>1120</RobaSifra>
<RobaNaziv>Kaiser tuna steak sa papricicom u ulju 170g.</RobaNaziv>
</item>
</Porudzbina>
If you change the data class like this:
[Atlas.Xml.XmlSerializationType(ChildElementName = "PorudzbenicaStavka")]
public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
public long KomSifra { get; set; }
public Guid KomId { get; set; }
// ...
}
public class PorudzbenicaStavka
{
public int rb { get; set; }
public string RobaSifra { get; set; }
[XmlText]
public string RobaNaziv { get; set; }
}
Then, serialized class would be like this:
<Porudzbina KomSifra="999999" KomId="ec63aec3-1512-451f-b967-836dd0e9820a">
<PorudzbenicaStavka rb="1" RobaSifra="3702">Foullon mlecna cokolada 33% Ecuador 100g</PorudzbenicaStavka>
<PorudzbenicaStavka rb="2" RobaSifra="1182">IL Capitano zelena maslina sa paprikom 720g</PorudzbenicaStavka>
<PorudzbenicaStavka rb="3" RobaSifra="1120">Kaiser tuna steak sa papricicom u ulju 170g.</PorudzbenicaStavka>
</Porudzbina>

How to create sets of the serialized objects C#

There are various types, in a special case which can be configured in different ways. How to serialize them?
[Serializable]
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
object _schemaVersion;
[XmlElement("SchemaVersion")]
public object SchemaVersion
{
get { return _schemaVersion; }
set { _schemaVersion = value; }
}
List<object> _test;
[XmlElement("Test")]
public List<object> Test
{
get { return _test; }
set { _test = value; }
}
public RootXml()
{
}
}
I.e. root can include different objects, and they have to be serialized...
I have a xml-format approximately of such
look:
<?xml version="1.0" encoding="windows-1251"?>
<RootXml>
<SchemaVersion Number="" />
<Report Code="">
<Period Code="" Date="">
<Source ClassCode="" Code="">
<Form Code="">
<Column Num="1" Name="" />
<Column Num="2" Name="" />
<Column Num="3" Name="" />
<Document>
<Data code="11" />
<Data code="12">
<Px Num="1" Value="1" />
<Px Num="2" Value="1" />
<Px Num="4" Value="2" />
<Px Num="5" Value="2" />
</Data>
<Data code="13" />
</Document>
</Form>
</Source>
</Period>
</Report>
</RootXml>
In which some elements can change a little (Document, Document with tags, Document with the status, etc.),
included in others (for example, report incl. in scheme) ... and do not know how to change in the future.
I want to construct a set of "formats" which will also have various components, to be substituted...
Maybe for this purpose you shouldn't use serialization, and to define
set of attributes, and a reflection to process objects and to form xml (approximately just as XmlSerializer)???
You are trying to serialize and deserialize data with polymorphic fields. You have a few options here:
If you know in advance all possible types that might be encountered in a polymorphic field, you can use attributes to tell XmlSerializer how to serialize and deserialize each type. In particular, for a polymorphic field, apply [XmlElement("DerivedElementName", Type = typeof(DerivedElementType))] for every derived type that might be encountered.
For instance, simplifying your RootXml class somewhat, the following allows for two different types of report to be serialized:
[XmlRoot("Report", Namespace = "")]
public class Report
{
[XmlAttribute]
public string Code { get; set; }
[XmlElement]
public decimal TotalCost { get; set; }
}
[XmlRoot("DifferentReport", Namespace = "fuuuu")]
public class DifferentReport
{
public DifferentReport() { }
public DifferentReport(string code, string value)
{
this.Code = code;
this.Value = value;
}
[XmlAttribute]
public string Code { get; set; }
[XmlText]
public string Value { get; set; }
}
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
public RootXml() { }
object _test;
[XmlElement("Report", Type=typeof(Report))]
[XmlElement("DifferentReport", Type = typeof(DifferentReport))]
public object Data
{
get { return _test; }
set { _test = value; }
}
}
And then later, both of the following can be serialized and deserialized:
var root1 = new RootXml { Data = new Report { Code = "a code", TotalCost = (decimal)101.01 } };
var root2 = new RootXml { Data = new DifferentReport { Code = "a different code", Value = "This is the value of the report" } };
Note that you can use the same technique with polymorphic lists, in which case the serializer will expect sequences of elements with the specified names:
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
public RootXml() { }
List<object> _test;
[XmlElement("Report", Type=typeof(Report))]
[XmlElement("DifferentReport", Type = typeof(DifferentReport))]
public List<object> Data
{
get { return _test; }
set { _test = value; }
}
}
If the XML could be anything and you don't know what it might contain (because you must deserialize XML from the future versions and reserialize it without data loss, for example) you may need to load your XML into an XDocument then manually search for data using Linq-to-XML. For information on how to do this, see here: Basic Queries (LINQ to XML).
You could adopt a hybrid approach where you load the XML into an XDocument, then deserialize and serialize familiar portions with XmlSerializer, using the following extension methods:
public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element)
{
return element.Deserialize<T>(new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = serializer.Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}
public static XElement Serialize<T>(this T obj, bool omitStandardNamespaces = true)
{
return obj.Serialize(new XmlSerializer(obj.GetType()), omitStandardNamespaces);
}
public static XElement Serialize<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces = true)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
(ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer.Serialize(writer, obj, ns);
}
return doc.Root;
}
}
Then use them to pick out and deserialize known portions of your XML as follows:
var doc = XDocument.Parse(xml);
var reportElement = doc.Root.Element("Report");
if (reportElement != null)
{
var report1 = doc.Root.Element("Report").Deserialize<Report>();
// Do something with the report.
// Create a different report
var differentReport = new DifferentReport { Code = report1.Code + " some more code", Value = "This is the value of the report" };
var differentElement = differentReport.Serialize();
reportElement.AddAfterSelf(differentElement);
reportElement.Remove();
}
OK, given that you are using c# 2.0, you can load your Xml into an XmlDocument and use it as described here: Process XML Data Using the DOM Model. This is a precursor API to Linq-to-XML and is somewhat harder to work with -- but nevertheless totally functional.
You can also adopt the hybrid approach and use XmlSerializer to deserialize and re-serialize known chunks of an XmlDocument. Here are some extension methods for this purpose -- but since you're using c# 2.0, you must remove the this keyword:
public static class XmlNodeExtensions
{
public static XmlElement SerializeToXmlElement<T>(this T o, XmlElement parent)
{
return SerializeToXmlElement(o, parent, new XmlSerializer(o.GetType()));
}
public static XmlElement SerializeToXmlElement<T>(this T o, XmlElement parent, XmlSerializer serializer)
{
int oldCount = parent.ChildNodes.Count;
XPathNavigator navigator = parent.CreateNavigator();
using (XmlWriter writer = navigator.AppendChild())
{
writer.WriteComment(""); // Kludge suggested here: https://social.msdn.microsoft.com/Forums/en-US/9ff20a3c-913d-4c6f-a18a-c10040290862/how-to-xmlserialize-directly-into-xmldocument?forum=asmxandxml
serializer.Serialize(writer, o);
}
XmlElement returnedElement = null;
for (int i = parent.ChildNodes.Count - 1; i >= oldCount; i--)
{
XmlComment comment = parent.ChildNodes[i] as XmlComment;
if (comment != null)
{
parent.RemoveChild(comment);
}
else
{
returnedElement = (parent.ChildNodes[i] as XmlElement) ?? returnedElement;
}
}
return returnedElement;
}
public static XmlDocument SerializeToXmlDocument<T>(this T o)
{
return SerializeToXmlDocument(o, new XmlSerializer(o.GetType()));
}
public static XmlDocument SerializeToXmlDocument<T>(this T o, XmlSerializer serializer)
{
XmlDocument doc = new XmlDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild())
serializer.Serialize(writer, o);
return doc;
}
public static T Deserialize<T>(this XmlElement element)
{
return Deserialize<T>(element, new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XmlElement element, XmlSerializer serializer)
{
using (var reader = new XmlNodeReader(element))
return (T)serializer.Deserialize(reader);
}
}
Given those methods, you can do things like:
// Load the document from XML
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
// Find all nodes with name "Report"
foreach (XmlElement reportNode in doc.SelectNodes("/RootXml/Report"))
{
// Deserialize as a Report
Report report = XmlNodeExtensions.Deserialize<Report>(reportNode);
// Do something with it
// Create a new Report, based on the original report.
DifferentReport differentReport = new DifferentReport(report.Code + " some more code", "This is the value of the report"); ;
// Add the new report to the children of RootXml
XmlElement newNode = XmlNodeExtensions.SerializeToXmlElement(differentReport, (XmlElement)reportNode.ParentNode);
}
As you can see this is quite similar to what is possible with Linq-to-XML.

Categories