XML deserialization failed to load values to element containing array of elements - c#

I have the following XML file:
<?xml version="1.0" encoding="UTF-8"?>
<TestConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Barcode>MB-B3-00</Barcode>
<TestSuites>
<Test>USB A Slave Port</Test>
<Test>USB B Host Port</Test>
</TestSuites>
</TestConfiguration>
I want to deserialize it into the following class:
public class TestConfiguration
{
private string _barcode;
private string[] _testSuites;
private string[] _testcase;
//Product barcode
public string Barcode
{
get{return this._barcode;}
set{this._barcode = value;}
}
//Test suites
[System.Xml.Serialization.XmlArrayItemAttribute("Test", IsNullable = false)]
public string[] Testsuites
{
get{return this._testSuites;}
set{this._testSuites = value;}
}
//individual test
[System.Xml.Serialization.XmlTextAttribute()]
public string[] Testcase
{
get{return this._testcase;}
set{this._testcase = value;}
}
}
My deserialization code is:
XmlSerializer serializer = new XmlSerializer(typeof(TestConfiguration));
StreamReader reader = new StreamReader(filename);
TestConfiguration _testConfig = (TestConfiguration)serializer.Deserialize(reader);
reader.Close();
However, _testConfig object only contains Barcode value and the properties Testcase and TestSuites are null. Any advice please?

You are very close. The name of your property, Testsuites, doesn't quite match the name of the element <TestSuites> - the capitalization of the letter S differs, and XML Tags are Case Sensitive.
To fix, rename the property or attach an XmlArrayAttribute with the correct element name:
//Test suites
[System.Xml.Serialization.XmlArray("TestSuites")]
[System.Xml.Serialization.XmlArrayItem("Test", IsNullable = false)]
public string[] Testsuites
{
get { return this._testSuites; }
set { this._testSuites = value; }
}

Try this. You can eliminate the tag and have an array of just element. I can show you how.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlSerializer serializer = new XmlSerializer(typeof(TestConfiguration));
StreamReader reader = new StreamReader(FILENAME);
TestConfiguration _testConfig = (TestConfiguration)serializer.Deserialize(reader);
reader.Close();
}
}
[XmlRoot("TestConfiguration")]
public class TestConfiguration
{
private string _barcode;
private string[] _testSuites;
private string[] _testcase;
//Product barcode
[XmlElement("Barcode")]
public string Barcode { get; set; }
//Test suites
[XmlElement("TestSuites")]
public TestSuites testSuites { get; set; }
}
//individual test
[XmlRoot("TestSuites")]
public class TestSuites
{
[XmlElement("Test")]
public List<string> test {get;set;}
}
}
​

Related

c# XmlSerializer deserializer missing default namespace

Below is an example payload response from an integration I am currently working on. The response does not set a default namespace (xml2 variable in example) and the issue is that XmlSerializer does not assume the default namespace is "d". I have tried setting the default namespace in the XMLSerializer constructor but that doesn't work either. As well, I can't expect the "service" to update/fix their side. Is there some other settings I can pass that will correctly populate the class?
Thanks,
Chuck
using System;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace TestXmlNamespace
{
[XmlRoot(ElementName = "root", Namespace = "my_test_ns1")]
public class Test
{
public string name { get; set; }
public int age { get; set; }
[XmlElement(Namespace = "my_test_ns2")]
public int ageInMonths { get; set; }
public override bool Equals(object obj)
{
return obj is Test b && name == b.name && age == b.age && ageInMonths == b.ageInMonths;
}
public void Run(string str, string name)
{
XmlSerializer serializer = new XmlSerializer(typeof(Test));
using (StringReader rStream = new StringReader(str))
{
Test test = serializer.Deserialize(rStream) as Test;
Console.Out.WriteLine(test.Equals(this) ? $"{name} equals expected" : $"{name} does not equal expected");
}
}
}
class Program
{
const string xml1 = #"<?xml version=""1.0"" encoding=""UTF-8"" ?>
<d:root xmlns:d=""my_test_ns1"" xmlns:v=""my_test_ns2"">
<d:name>Bill</d:name>
<d:age>32</d:age>
<v:ageInMonths>384</v:ageInMonths>
</d:root>
";
const string xml2 = #"<?xml version=""1.0"" encoding=""UTF-8"" ?>
<d:root xmlns:d=""my_test_ns1"" xmlns:v=""my_test_ns2"">
<name>Bill</name>
<age>32</age>
<v:ageInMonths>384</v:ageInMonths>
</d:root>
";
static void Main(string[] args)
{
Test expected = new Test()
{
name = "Bill",
age = 32,
ageInMonths = 384
};
expected.Run(xml1, "xml1");
expected.Run(xml2, "xml2");
}
}
}
I found the solution and I am posting it for those in the future who may run across this problem and hopefully I will help them save some time.
Simply put, when an element does not have a namespace prefix XmlSerializer treats it has not part of a name space so specifying an empty name space in the element attribute handles this. See the updated example code below.
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace TestXmlNamespace
{
public class CustomXmlTextReader : XmlTextReader
{
private readonly string DefaultNamespace = String.Empty;
public CustomXmlTextReader(StringReader reader, string defaultNamespace) : base(reader)
{
DefaultNamespace = defaultNamespace;
}
public override string NamespaceURI => String.IsNullOrEmpty(base.NamespaceURI) ? DefaultNamespace : base.NamespaceURI;
};
[XmlRoot(ElementName = "root", Namespace = "my_test_ns1")]
public class Test
{
[XmlElement("name", Namespace = "")] // Set namespace to empty string when no namespace prefix specified in xml
public string name { get; set; }
[XmlElement("age", Namespace = "")] // Set namespace to empty string when no namespace prefix specified in xml
public int age { get; set; }
[XmlElement(Namespace = "my_test_ns2")]
public int ageInMonths { get; set; }
public override bool Equals(object obj)
{
return obj is Test b && name == b.name && age == b.age && ageInMonths == b.ageInMonths;
}
public void Run(string str, string name)
{
using (StringReader rStream = new StringReader(str))
{
//using (XmlTextReader tr = new CustomXmlTextReader(rStream, "my_test_ns1"))
using (XmlTextReader tr = new XmlTextReader(rStream))
{
XmlSerializer serializer = new XmlSerializer(typeof(Test));
Test test = serializer.Deserialize(tr) as Test;
Console.Out.WriteLine(test.Equals(this) ? $"{name} equals expected" : $"{name} does not equal expected");
}
}
}
}
class Program
{
const string xml = #"<?xml version=""1.0"" encoding=""UTF-8"" ?>
<d:root xmlns:d=""my_test_ns1"" xmlns:v=""my_test_ns2"">
<name>Bill</name>
<age>32</age>
<v:ageInMonths>384</v:ageInMonths>
</d:root>
";
static void Main(string[] args)
{
Test expected = new Test()
{
name = "Bill",
age = 32,
ageInMonths = 384
};
expected.Run(xml, "xml1");
}
}
}

C# XML Serialization How To Set Attribute xsi:type

This is how my Xml should look after XML Serialization:
<value xsi:type="CD" otherAttributes= "IDK">
.
.
.
</value>
Thats my C# code to it:
public class Valué
{
[XmlAttribute(AttributeName ="xsi:type")]
public string Type { get; set; } = "CD";
[XmlAttribute(attributeName: "otherAttributes")]
public string OtherAttributes { get; set; } = "IDK"
}
Apparently the XmlSerializer can't serialize colons (:) in attributenames.... how do i fix this problem?
If i remove the colon from the attributeName itm works out fine ..
Do it the correct way :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
Valué value = new CD() { OtherAttributes = "IDK" };
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(FILENAME, settings);
XmlSerializer serializer = new XmlSerializer(typeof(Valué));
serializer.Serialize(writer, value);
}
}
[XmlInclude(typeof(CD))]
public class Valué
{
}
public class CD : Valué
{
[XmlAttribute(attributeName: "otherAttributes")]
public string OtherAttributes { get; set; }
}
}

Serialize XML Attribute of Subelement

I would like to directly serialize an attribute of an Subelement with an occurence of 0..1 into a property, like so:
public class Root {
// Which attributes are needed here?
public TestEnum TestEnum { get; set; }
}
public enum TestEnum {
[XmlEnum("tst1")]
Test1,
[XmlEnum("tst2")]
Test2
}
The XML Structure simplified:
<root>
<Element testType="tst1"/>
</root>
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(Root));
Root root = (Root)serializer.Deserialize(reader);
}
}
[XmlRoot("root")]
public class Root
{
// Which attributes are needed here?
private TestEnum TestEnum { get; set; }
public Element Element
{
get { Element e = new Element(); e.testType = TestEnum.ToString(); return e; }
set { TestEnum = (TestEnum)Enum.Parse(typeof(TestEnum), value.testType); }
}
}
public class Element
{
[XmlAttribute]
public string testType { get; set; }
}
public enum TestEnum
{
tst1,
tst2
}
}

Read xml attribute as object

I have the following simple XML file and I am tying to read the attribute code, using a C# .NET Core Console. I really appetite for any help.
<course type="IT" date="19.09.2019">
<line code="IT001"/>
</course>
UPDATE
I need the result as an object.
Use xml serialization to get a class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Globalization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(Course));
Course course = (Course)serializer.Deserialize(reader);
}
}
[XmlRoot("course")]
public class Course
{
[XmlAttribute("type")]
public string _type { get; set; }
public DateTime _date { get; set; }
[XmlAttribute("date")]
public string date {
get { return _date.ToString("dd.MM.yyyy"); }
set { _date = DateTime.ParseExact(value, "dd.MM.yyyy", System.Globalization.CultureInfo.InvariantCulture); }
}
private string _code { get; set; }
[XmlElement("line")]
public Line line
{
get { return new Line() { code = _code }; }
set { _code = value.code; }
}
}
[XmlRoot("line")]
public class Line
{
[XmlAttribute("code")]
public string code { get; set; }
}
}
Many ways to skin this cat. Here is a solution by making use of XPath
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml("<course type=\"IT\" date=\"19.09.2019\"> <line code=\"IT001\"/></course>");
var attrVal = xmlDoc.SelectSingleNode("/course/line/#code").Value;
Console.WriteLine("Attribute 'code' value is " + attrVal);

Deserialize XML document, keeping track of order

I am parsing an XML document using the XMLSerialization tool. The sample XML file consists of paragraphs (string) and tables, which are a complex XML type. Tables consist of a series of row, which consists of a series of entry (string)
I need to keep track of the position of each table, relative to each paragraph. Is there a way of catching the position of each table as it is being parsed by the XMLSerialization tool? Or do I need to use a construct like [XMLAnyElement] and parse each paragraph and table sequentially in order to track the table position? I would prefer to avoid that approach, because my real XML files have many levels that would need manual parsing. I have a feeling that I am missing something really obvious here, but I've been scouring SO and trying multiple approaches, but with no straightforward solution.
Here is my basic code:
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace XMLDeserializeTest
{
class Program
{
static void Main(string[] args)
{
string file = Environment.CurrentDirectory + #"\test.xml";
test testClass = Deserialize(file);
}
static test Deserialize(string url)
{
XmlSerializer reader =
new XmlSerializer(typeof(test));
StreamReader stream = new StreamReader(url);
return reader.Deserialize(stream) as test;
}
}
public class test
{
[XmlElement("paragraph")]
public List<string> paragraphs { get; set; }
[XmlElement("table")]
public List<Table> tables { get; set; }
public test()
{
}
}
public class Table
{
[XmlElement("row")]
public List<Row> rows { get; set; }
public int nodeNumber { get; set; } // This is what needs to be tracked
public Table()
{
}
}
public class Row
{
[XmlElement("entry")]
public List<string> entries { get; set; }
public Row()
{
}
}
My sample XML:
<?xml version="1.0" encoding="utf-8" ?>
<test>
<paragraph>Here is some text.</paragraph>
<paragraph>Here is some more text. The table follows this paragraph.</paragraph>
<table>
<row>
<entry>1</entry>
<entry>2</entry>
<entry>3</entry>
</row>
<row>
<entry>4</entry>
<entry>5</entry>
<entry>6</entry>
</row>
</table>
<paragraph>This is the last paragraph. This comes after the table.</paragraph>
</test>
I came up with one solution, using XDocument, but it seems pretty clumsy:
XDocument Xdoc = XDocument.Load(file);
int numParagraphs = 0;
int tableNumber = 0;
foreach(XElement item in Xdoc.Root.Descendants())
{
if (item.Name.LocalName.Equals("paragraph"))
{
numParagraphs++;
}
else if (item.Name.LocalName.Equals("table"))
{
testClass.tables[tableNumber].nodeNumber = numParagraphs;
tableNumber++;
}
}
One option would just be to serialize the paragraph "index" of the table into your XML at the time of serialization. That way you wouldn't have to do anything custom.
However, to do what you are looking for with the XmlSerializer you could handle the deserialization yourself for certain element types using the UnknownElement event. Notice that the XmlElement attributes have been removed from the test class in order for the table and paragraph elements to be handled.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace XMLDeserializeTest
{
class Program
{
static int paragraphCount = 0;
static void Main(string[] args)
{
string file = Environment.CurrentDirectory + #"\test.xml";
paragraphCount = 0;
test testClass = Deserialize(file);
}
static test Deserialize(string url)
{
XmlSerializer serializer = new XmlSerializer(typeof(test));
serializer.UnknownElement += serializer_UnknownElement;
StreamReader stream = new StreamReader(url);
return serializer.Deserialize(stream) as test;
}
static void serializer_UnknownElement(object sender, XmlElementEventArgs e)
{
test t = (test)e.ObjectBeingDeserialized;
if (e.Element.Name == "table")
{
var s = new XmlSerializer(typeof(Table));
var sr = new StringReader(e.Element.OuterXml);
Table newTable = s.Deserialize(sr) as Table;
newTable.nodeNumber = paragraphCount;
t.tables.Add(newTable);
}
else if (e.Element.Name == "paragraph")
{
String paragraphText = e.Element.InnerText;
t.paragraphs.Add(paragraphText);
paragraphCount++;
}
}
}
public class test
{
public List<string> paragraphs { get; set; }
public List<Table> tables { get; set; }
public test()
{
}
}
[Serializable, XmlRoot("table")]
public class Table
{
[XmlElement("row")]
public List<Row> rows { get; set; }
public int nodeNumber { get; set; } // This is what needs to be tracked
public Table()
{
}
}
[Serializable, XmlRoot("row")]
public class Row
{
[XmlElement("entry")]
public List<string> entries { get; set; }
public Row()
{
}
}
}

Categories

Resources