Serializing list of nullable CDATA - c#

I am trying to deserialize a XML file:
<?xml version="1.0" encoding="UTF-8"?>
<Foos xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FooList>
<Foo>
<Bar>bar value</Bar>
<Stack />
</Foo>
<Foo>
<Bar>bar value</Bar>
<Stack><![CDATA[This is some cdata, where <br> html code is left as is..]]></Stack>
</Foo>
</FooList>
</Foos>
Into the following class
[XmlRoot("Foos")]
public class Foos
{
[XmlArray("FooList")]
[XmlArrayItem("Foo")]
public List<Foo> FooList { get; set; }
}
public class Foo
{
public string Bar { get; set; }
[XmlElement("Stack", typeof(XmlCDataSection))]
public XmlCDataSection Stack {get; set; }
}
The problem is, that the resulting FooList of Foos only contains one (1) element, which is the first Foo block, with the Slack property set to null. If i add a CDATA value to stack, like with the second Foo block, then i will end up with a list of both elements.
For some reason, the deserialization stops, after reaching a CDATA value which is null.
I have tried creating a private string and creating the setter and getter of Stack, such that is uses the private property to store the CDATA string. This doesn't change anything.
Any suggestions?
Thank you.

Get to a cdata section through a string. Object model:
[Serializable]
[XmlRoot("Foos", Namespace="", IsNullable=false)]
public partial class Foos {
[XmlArrayItem ("Foo", IsNullable=false)] public Foo[] FooList { get; set; } }
[Serializable] public partial class Foo {
public string Bar { get; set; }
[XmlIgnore] public string Stack { get; set; }
[XmlElement("Stack", IsNullable=true)]
public XmlCDataSection StackCDATA {
get { return new XmlDocument().CreateCDataSection(Stack ?? String.Empty); }
set {
Stack = value == null
? String.Empty
: ( String.IsNullOrWhiteSpace(value.Value)
? String.Empty
: value.Value); } }
public override string ToString() { return String.Format("Bar:{0} Stack:{1}", Bar, Stack); } }
The right way to specify a null element is <Stack xsi:nil='true' />. With this addition, your deserialization will be flawless. However, on the first occurrence of <Stack />, the deserializer will encounter an unknown node. Workaround in case you are missing this attribute, subscribe to UnknownElement:
var fooSrlz = new XmlSerializer(typeof(Foo), new XmlRootAttribute { ElementName = "Foo", IsNullable = true} );
var foosMissedByDeserializer = new List<CData.Foo>();
srlz.UnknownElement += (/*object*/ sender, /*XmlElementEventArgs */ e) => {
using (TextReader tr = new StringReader(e.Element.OuterXml)) {
var foo = (CData.Foo)fooSrlz.Deserialize(tr);
foosMissedByDeserializer.Add (foo);
} };
Merge the deserialization result with the foosMissedByDeserializer above.

Related

Deserialize into object from not nested xml

Imaging an XML-file like this:
<?xml version="1.0" encoding="UTF-16"?>
<treffer>
<prod_internid>123456789</prod_internid>
<md_nr>123123</md_nr>
<md_mart_id>4</md_mart_id>
<md_mtyp_nr>9876</md_mtyp_nr>
<mra_th>
<ie_th_pth>-1</ie_th_pth>
<ie_th_ea_bez>Fehler: Keine Angabe</ie_th_ea_bez>
</mra_th>
</treffer>
As you can see, there are three tags with <md_XY></md_XY>.
I want to deserialize them into an object that looks like this:
public class DeMedienXmlDto
{
[XmlElement("md_nr")]
public int MedienNr { get; set; }
[XmlElement("md_mart_id")]
public int MedienArtId { get; set; }
[XmlElement("md_mtyp_nr")]
public string MedienTypId { get; set; }
}
But this should be a property of the whole deserialized object:
[XmlRoot("treffer")]
public class DeAnalyseArtikelXmlDto
{
[XmlElement("prod_internid")]
public long ArtikelId { get; set; }
[XmlElement("treffer")]
public DeMedienXmlDto Medium { get; set; }
[XmlElement("mra_th")]
public List<DeThemenXmlDto> Themen { get; set; }
}
I've tried annotating the Medium property with [XmlElement("treffer")] since the tags are childs of <treffer> but that didn't work...
Deserializing the <mra_th>...</mra_th> works since I can annotate the list with the grouped tag but I don't have such a tag for <md...>.
How can I achieve this?
My xml deserializer looks like this:
public class XmlDeserializer : IXmlDeserializer
{
public T Deserialize<T>(string xmlFilename)
{
var returnObject = default(T);
if (string.IsNullOrEmpty(xmlFilename)) return default(T);
try
{
var xmlStream = new StreamReader(xmlFilename);
var serializer = new XmlSerializer(typeof(T));
returnObject = (T)serializer.Deserialize(xmlStream);
}
catch (Exception exception) {
LogHelper.LogError($"Das XML-File {xmlFilename} konnte nicht deserialisiert werden: {exception.Message}");
throw;
}
return returnObject;
}
}
Thanks in advance
Edit (to clarify):
I want the following tags deserialized into an object of type DeMedienXmlDto:
<md_nr>
<md_mart_id>
<md_mtyp_nr>
This is not how XmlSerializer works. The class structure must correspond to structure of the XML in order to work automatically.
This:
[XmlElement("treffer")]
public DeMedienXmlDto Medium { get; set; }
doesn't work, because there is no nested <treffer> element. The XmlElementAttribute cannot denote the parent (surrounding) element.
The are two options how to solve your situation:
Use a separate set of classes for deserialization, and a separate set representing your DTO objects. You'd then need to create a mapping.
Implement IXmlSerializable on DeAnalyseArtikelXmlDto and parse the inner XML yourself.

Why XmlSerializer.Order do this

Can anybody explain why I am getting result below?
Class that should be instantiated:
[System.SerializableAttribute()]
public class SampleClass
{
[System.Xml.Serialization.XmlElementAttribute(Order = 10)]
public string Foo { get; set; }
[System.Xml.Serialization.XmlElementAttribute(Order = 5)]
public string Bar { get; set; }
}
XML used for de-serialization:
<?xml version="1.0" encoding="utf-8" ?>
<SampleClass>
<Foo>Test1</Foo>
<Bar>Test2</Bar>
</SampleClass>
I see Foo = Test1 (correct) and Bar = null (not correct) in de-serialized class.
This is all about Order attribute. It is on purpose set to wrong values. Everything works fine if values are 0 and 1.
Name and Order criteria do not match for both fields but for some reason one field is de-serialized while second one does not. I would rather expect to see Exception or both values = null or both fields resolved and de-serialized.
Is there any explanation for that?
You do see a serialization error actually.
It really comes down to design preference. The developers decided that they didn't want to except and abort the entire deserialization process, but rather notify and continue.
Using your XML.
<?xml version="1.0" encoding="utf-8" ?>
<SampleClass>
<Foo>Test1</Foo>
<Bar>Test2</Bar>
</SampleClass>
Consider this code to deserialize it.
XmlSerializer xs = new XmlSerializer(typeof(SampleClass));
XmlDeserializationEvents events = new XmlDeserializationEvents();
events.OnUnknownAttribute = (sender, e) => Debug.WriteLine("Unknown Attributed");
events.OnUnknownElement = (sender, e) => Debug.WriteLine("Unknwon Element");
events.OnUnknownNode = (sender, e) => Debug.WriteLine("Unknown Node");
events.OnUnreferencedObject = (sender, e) => Debug.WriteLine("Unreferenced Object");
SampleClass cs_de = (SampleClass)xs.Deserialize(XmlReader.Create(new StringReader(xml)), events);
Debug.WriteLine(cs_de.Foo);
Debug.WriteLine(cs_de.Bar);
When I use the correct ordering.
[System.Xml.Serialization.XmlElementAttribute(Order = 0)]
public string Foo { get; set; }
[System.Xml.Serialization.XmlElementAttribute(Order = 1)]
public string Bar { get; set; }
My output is
Foo
Bar
When I use the incorrect ordering.
[System.Xml.Serialization.XmlElementAttribute(Order = 10)]
public string Foo { get; set; }
[System.Xml.Serialization.XmlElementAttribute(Order = 5)]
public string Bar { get; set; }
My output is
Unknown Node
Unknwon Element
Foo
So why does Foo print out? Well my understanding is that Order is NOT the index. The Ordering only specifies that Foo needs to appear before Bar, not that Foo needs to be the 10th element and Bar needs to be the 5th. I think this makes the deserializer more flexible.

Deserialize xml node's attributes to class

I have two classes that look like this:
[XmlRoot("Foo")]
public class Foo
{
[XmlArray("BarResponse")]
[XmlArrayItem("Bar")]
public List<Bar> bar {get; set;}
//some other elements go here.
}
[XmlRoot("Bar")]
public class Bar
{
[XmlAttribute("id")]
public Int32 Id {get; set;}
//some other elements go here.
}
The xml I'm receiving looks like this:
<?xml version="1.0"?>
<Foo>
<BarResponse>
<Bar id="0" />
<Bar id="1" />
</BarResponse>
</Foo>
When I attempt to deseralize this, I get an instance of the "Foo" class, and bar has one element in it, with all of it's properties null or default. Where am I going wrong?
try this:
[TestFixture]
public class BilldrTest
{
[Test]
public void SerializeDeserializeTest()
{
var foo = new Foo();
foo.Bars.Add(new Bar { Id = 1 });
foo.Bars.Add(new Bar { Id = 2 });
var xmlSerializer = new XmlSerializer(typeof (Foo));
var stringBuilder = new StringBuilder();
using (var stringWriter = new StringWriter(stringBuilder))
{
xmlSerializer.Serialize(stringWriter, foo);
}
string s = stringBuilder.ToString();
Foo deserialized;
using (var stringReader = new StringReader(s))
{
deserialized = (Foo) xmlSerializer.Deserialize(stringReader);
}
Assert.AreEqual(2,deserialized.Bars.Count);
}
}
[XmlRoot("Foo")]
public class Foo
{
public Foo()
{
Bars= new List<Bar>();
}
[XmlArray("BarResponses")]
[XmlArrayItem(typeof(Bar))]
public List<Bar> Bars { get; set; }
//some other elements go here.
}
[XmlRoot("Bar")]
public class Bar
{
[XmlAttribute("id")]
public Int32 Id { get; set; }
//some other elements go here.
}
You can find info on the use of XmlRoot here
ArrayItemAttribute is used if you expect the array to contain different types
You would get the same result stripping all attributes except for [XmlAttribute("id")], but I guess this is an excerpt from a context where it all is justified.
You need to add a default constructor for the Foo class that instantiates your List<Bar>.
[Serializable]
public class Foo
{
public Foo()
{
Bar = new List<Bar>();
}
[XmlArray("BarResponse")]
[XmlArrayItem("Bar")]
public List<Bar> Bar { get; set; }
}
[Serializable]
public class Bar
{
[XmlAttribute("id")]
public Int32 Id { get; set; }
}
Writes/Reads xml as:
<?xml version="1.0" encoding="utf-8"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<BarResponse>
<Bar id="0" />
<Bar id="1" />
</BarResponse>
</Foo>

Controlling XML Serialization of a List<> derived class

I'm converting a Dictionary object into a List<> derived class by means of the following two declarations:
[Serializable]
public class LogItem
{
public string Name { get; set; }
public string Value { get; set; }
public LogItem(string key, string value)
{
Name = key; Value = value;
}
public LogItem() { }
}
public class SerializableDictionary : List<LogItem>
{
public SerializableDictionary(Dictionary<string, string> table)
{
IDictionaryEnumerator index = table.GetEnumerator();
while (index.MoveNext())
{
Put(index.Key.ToString(), index.Value.ToString());
}
}
private void Put(string key, string value)
{
base.Add(new LogItem(key, value));
}
}
I intend to serialize SerializableDictionary by means of the following code:
SerializableDictionary log = new SerializableDictionary(contents);
using (StringWriter xmlText = new StringWriter())
{
XmlSerializer xmlFormat =
new XmlSerializer(typeof(SerializableDictionary), new XmlRootAttribute("Log"));
xmlFormat.Serialize(xmlText, log);
}
Works fine, but I'm unable to change the XML formatting.
This XML document is intended to be sent to a xml database field and is not meant to be deserialized.
I'd rather have both Name and Value
formatted as attributes of the
LogItem element.
However any attempts at using XMLAttribute have resulted in Reflection or compilation errors. I'm at loss as to what I can do to achieve this requirement. Could someone please help?
you can annotate them with XmlAttribute:
[Serializable]
public class LogItem
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }
public LogItem(string key, string value)
{
Name = key; Value = value;
}
public LogItem() { }
}
This worked fine for me and produced the following XML (based on sample input):
<?xml version="1.0" encoding="utf-16"?>
<Log xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LogItem Name="foo" Value="bar" />
<LogItem Name="asdf" Value="bcxcvxc" />
</Log>
There must be something else going on besides what you are showing. I am able to compile and execute the above code both with and without the XmlAttribute attribute applied to the Name and Value properties.
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }

Getting rid of an array name in C# XML Serialization

I'm trying to get to this result while serializing XML:
<Root Name="blah">
<SomeKey>Eldad</SomeKey>
<Element>1</Element>
<Element>2</Element>
<Element>3</Element>
<Element>4</Element>
</root>
Or in other words - I'm trying to contain an array within the "root" element, alongside additional keys.
This is my crude attempt:
[XmlRootAttribute(ElementName="Root", IsNullable=false)]
public class RootNode
{
[XmlAttribute("Name")]
public string Name { get; set; }
public string SomeKey { get; set; }
[XmlArrayItem("Element")]
public List<int> Elements { get; set; }
}
And my serialization:
string result;
XmlSerializer serializer = new XmlSerializer(root.GetType());
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
serializer.Serialize(sw, root);
result = sw.ToString();
}
However, this is my result (Removed the namespace for clarity):
<Root>
<SomeKey>Eldad</SomeKey>
<Elements>
<Element>1</Element>
<Element>2</Element>
<Element>3</Element>
</Elements>
</Root>
Is there any way to remove the "Elements" part?
Use XmlElement attribute on the Array, this will instruct the serializer to serialize the array items as child elements of the current element and not create a new root element for the array.
[XmlRootAttribute(ElementName="Root", IsNullable=false)]
public class RootNode
{
[XmlAttribute("Name")]
public string Name { get; set; }
public string SomeKey { get; set; }
[XmlElement("Element")]
public List<int> Elements { get; set; }
}
Thanks to Chris Taylor for the answer to my problem too. Using an asmx web service, I was getting the following XML:
<Manufacturers>
<Manufacturer>
<string>Bosch</string>
<string>Siemens</string>
</Manufacturer>
</Manufacturers>
I wanted to get the manufacturer names directly in the the element, getting rid of the element like this:
<Manufacturers>
<Manufacturer>Bosch</Manufacturer>
<Manufacturer>Siemens</Manufacturer>
</Manufacturers>
For anyone else with the same problem, my code to achieve this (in VB.Net) is:
<WebMethod()> _
Public Function GetManufacturers() As Manufacturers
Dim result As New Manufacturers
result.Manufacturer.Add("Bosch")
result.Manufacturer.Add("Siemens")
Return result
End Function
<XmlRoot(ElementName:="Manufacturers")> _
Public Class Manufacturers
<XmlElement("Manufacturer")> _
Public Manufacturer As New List(Of String)
End Class

Categories