Why do I keep getting an error when deserializing XML collection? - c#

I have almost 1K rows of XML data and I need to deserialize them. There are no errors in the serialize / deserialize block, but my collection seems to be empty after everything is done.
#1 Main code:
public XmlSerializer xmlSerializer = new XmlSerializer(typeof(Units));
public MainForm()
{
using (FileStream filestream = new FileStream("units.xml", FileMode.Open))
{
Units units = xmlSerializer.Deserialize(filestream) as Units;
MessageBox.Show("XML COUNT: " + units.AllUnits.Count.ToString());
}
/* some code here */
}
#2 Data class:
[Serializable, XmlRoot("Units")]
public class Units
{
[XmlArray("Units")]
[XmlArrayItem("Unit")]
public List<Unit> AllUnits { get; set; }
public Units() { }
}
[Serializable, XmlType("Unit")]
public class Unit
{
[XmlElement("Type")]
public String Type { get; set; }
[XmlElement("Name")]
public String Name { get; set; }
[XmlArray("ConvertTo")]
[XmlArrayItem("Value")]
public List<Value> ConvertTo { get; set; }
public Unit() { }
}
[Serializable]
public struct Value
{
[XmlAttribute("Name")]
public String Name { get; set; }
[XmlElement("Value")]
public Double ConvertValue { get; set; }
}
#3 XML data:
<Units>
<!-- ===================== AREA ===================== -->
<Unit>
<Name>SquareMeter</Name>
<Type>Area</Type>
<ConvertTo>
<Value Name = "SquareMeter">1</Value>
<Value Name = "SquareKilometer">0.000001</Value>
<Value Name = "SquareCentimeter">10000</Value>
<Value Name = "SquareMillimeter">1000000</Value>
<Value Name = "SquareMicrometer">1000000000000</Value>
<Value Name = "Hectare">0.0001</Value>
<Value Name = "SquareMile">0.00000038610215855</Value>
<Value Name = "SquareYard">1.19599</Value>
<Value Name = "SquareFoot">10.7639</Value>
<Value Name = "SquareInch">1550</Value>
<Value Name = "Acre">0.000247105</Value>
</ConvertTo>
</Unit>
<Unit>
...
</Unit>
</Unit>
I also tried json for this kind of data but it seems to be a bug in my data class (#2). What should I do to make it work?

I have made a few tweaks to your attributes.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestProject1Core
{
[Serializable, XmlRoot("Units")]
public class Units
{
[XmlElement("Unit")]
public List<Unit> AllUnits { get; set; } = new List<Unit>();
public Units() { }
}
[Serializable, XmlType("Unit")]
public class Unit
{
[XmlElement("Type")]
public String Type { get; set; }
[XmlElement("Name")]
public String Name { get; set; }
[XmlArray("ConvertTo")]
[XmlArrayItem("Value", ElementName ="Value")]
public List<Value> ConvertTo { get; set; }
public Unit() { }
}
[Serializable]
public struct Value
{
[XmlAttribute("Name")]
public String Name { get; set; }
[XmlText]
public Double ConvertValue { get; set; }
}
[TestClass]
public class UnitTestXml
{
[TestMethod]
public void TestXmlWrite()
{
var xmlSerializer = new XmlSerializer(typeof(Units));
Units units = new Units();
var converts = new List<Value>()
{
new Value() { Name="test", ConvertValue=1},
new Value() { Name="test2", ConvertValue=2}
};
units.AllUnits.Add(new Unit() { Name="test", Type = "some type", ConvertTo= converts });
units.AllUnits.Add(new Unit() { Name = "test2", Type = "some type2", ConvertTo = converts });
using (StringWriter writer = new StringWriter())
{
xmlSerializer.Serialize(writer, units);
var x = writer.ToString();
}
}
[TestMethod]
public void TestXml()
{
var xmlSerializer = new XmlSerializer(typeof(Units));
string xmlString = File.ReadAllText("testData.xml");
Units units = xmlSerializer.Deserialize(new StringReader(xmlString)) as Units;
Assert.IsTrue(units.AllUnits.Count > 0);
}
}
}

Related

Deserialize XML with duplicate nested tags

I'm deserialization the results of a request that has the same tag repeated at multiple levels, I have it working but to do so I'm changing the format of the XML before attempting to deserialize it.
I'm not able to edit the source of the XML to change it to only have Diary at one level.
Is there a way to adjust my XML attributes to handle the deserialization without needing to adjust the response?
XML Response
<?xml version="1.0" ?>
<Root>
<DiaryDetails>
<Diary>
<Diary created_user="value1" created_date="value2" long_text="value3" short_text="value4" entry_type="value5" >Value6</Diary>
</Diary>
<Diary>
<Diary created_user="value7" created_date="value8" long_text="value9" short_text="value10" entry_type="value11" >Value12</Diary>
</Diary>
</DiaryDetails>
</Root>
Class definition
[XmlRoot("DiaryDetails")]
public class Diaries : List<Diary> { }
[XmlRoot("Diary")]
public class Diary
{
[XmlAttribute("created_user")]
public string CreatedBy { get; set; }
[XmlAttribute("created_date")]
public string CreatedDate { get; set; }
[XmlAttribute("long_text")]
public string LongText { get; set; }
[XmlAttribute("short_text")]
public string ShortText { get; set; }
[XmlAttribute("entry_type")]
public string Type { get; set; }
}
Deserialization Method
internal T DeserilaiseObject<T>(string response)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
T DeserilaisedObject;
using (TextReader reader = new StringReader(response))
{
DeserilaisedObject = (T)serializer.Deserialize(reader);
}
return DeserilaisedObject;
}
I'm currently handling this with a string replace:
response = response.Replace("<Diary><Diary", "<Diary").Replace("</Diary></Diary>", "</Diary>");
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication40
{
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
{
[XmlArray("DiaryDetails")]
[XmlArrayItem("Diary")]
public List<DiaryMain> diaries { get; set; }
}
public class DiaryMain
{
public Diary Diary { get; set; }
}
[XmlRoot("Diary")]
public class Diary
{
[XmlAttribute("created_user")]
public string CreatedBy { get; set; }
[XmlAttribute("created_date")]
public string CreatedDate { get; set; }
[XmlAttribute("long_text")]
public string LongText { get; set; }
[XmlAttribute("short_text")]
public string ShortText { get; set; }
[XmlAttribute("entry_type")]
public string Type { get; set; }
}
}
Assuming that you are only interested at the attributed Diary elements at the second nesting level you could do this:
// load your xml into a document
var doc = new XmlDocument();
doc.Load("your_xml_file.xml");
// extract the interesting nodes using xpath
var nodes = doc.SelectNodes("//Diary/Diary");
// deserialize the filtered NodeList (yes it's that clunky)
var serializer = new XmlSerializer(typeof(Diary));
var diaries = nodes.Cast<XmlNode>().Select(x => serializer.Deserialize(new XmlNodeReader(x))).Cast<Diary>().ToArray();

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 add Xml attribute to a property at runtime

I need to serialize a class to xml. If a certain condition is met at run-time, I want to add an XML attribute to an element and assign it a value. Sometimes, the "Error" attribute will appear and sometimes it won't.
My code that serializes my objects:
public class XmlToolsRepo : IXmlTools
{
public string SerializeToXML<T>(object obj)
{
string results = null;
Encoding enc = Encoding.UTF8;
using (MemoryStream ms = new MemoryStream())
{
using (XmlTextWriter xw = new XmlTextWriter(ms, enc))
{
xw.Formatting = Formatting.None;
XmlSerializerNamespaces emptyNS = new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") });
XmlSerializer xSerializer = new XmlSerializer(typeof(T));
xSerializer.Serialize(xw, obj, emptyNS);
}
results = enc.GetString(ms.ToArray());
}
return results;
}
}
A class with a property that could have a new attribute at run-time:
[DataContract]
public class H204
{
[DataMember]
[XmlAttribute]
public string Code { get; set; }
[DataMember]
public string DW { get; set; }
}
When a condition is met I need for the XML to look like this:
<?xml version="1.0" encoding="UTF-8"?>
<H204 Code="A">
<DW Error="test" />
</H204>
Try following :
public class H204
{
[XmlAttribute(AttributeName = "Code")]
public string Code { get; set; }
[XmlElement(ElementName = "DW")]
public DW dw{ get; set; }
}
public class DW
{
[XmlAttribute(AttributeName = "Error")]
public string text { get; set; }
}

C# SelectNodes for main node attribute

I'm trying to obtain the value of the logicalName attribute of the main node of this xml file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<ticketlayout xmlns="http://www.example.com/ticketlayout" logicalName="target.xml" deviceCode="1" measurement="mm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.com/ticketlayout">
<fontdefinition id="BarCode">
<fontname>Code128bWin</fontname>
<size measure="pt">16</size>
</fontdefinition>
</ticketlayout>
I've tried to add the namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance" this way:
XmlDocument fLayout = new XmlDocument();
fLayout.Load("myFile.xml");
XmlNamespaceManager nsmRequestLayout = new XmlNamespaceManager(fLayout.NameTable);
nsmRequestLayout.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
string sValue = fLayout.SelectNodes("//ticketlayout", nsmRequestLayout)[0].Attributes["name"].Value;
But I get no nodes. I've tried without namespace and no nodes again, and son on.
¿Could please anyone help me?
Thanks in advance.
If you want to get the value : target.xml
Try this code
XmlDocument fLayout = new XmlDocument();
fLayout.Load("myFile.xml"); // your XML file
var attrib = fLayout["ticketlayout"].Attributes["logicalName"].Value;
First of all your XML is not valid.
I modified to look this way in order to achieve what you are looking for.
XML File :
<?xml version="1.0" encoding="UTF-8"?>
<ticketlayout xmlns="http://www.example.com/ticketlayout" logicalName="target.xml" deviceCode="1" measurement="mm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.com/ticketlayout">
<fontdefinition id="BarCode">
<fontname>Code128bWin</fontname>
<size measure="pt">16</size>
</fontdefinition>
</ticketlayout>
I am not sure why you would not have a model structure to deserialize you xml, and then access whatever property/attribute you need.
Example :
Classes:
[XmlRoot(ElementName = "size", Namespace = "http://www.example.com/ticketlayout")]
public class Size
{
[XmlAttribute(AttributeName = "measure")]
public string Measure { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "fontdefinition", Namespace = "http://www.example.com/ticketlayout")]
public class Fontdefinition
{
[XmlElement(ElementName = "fontname", Namespace = "http://www.example.com/ticketlayout")]
public string Fontname { get; set; }
[XmlElement(ElementName = "size", Namespace = "http://www.example.com/ticketlayout")]
public Size Size { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
[XmlRoot(ElementName = "ticketlayout", Namespace = "http://www.example.com/ticketlayout")]
public class Ticketlayout
{
[XmlElement(ElementName = "fontdefinition", Namespace = "http://www.example.com/ticketlayout")]
public Fontdefinition Fontdefinition { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
[XmlAttribute(AttributeName = "logicalName")]
public string LogicalName { get; set; }
[XmlAttribute(AttributeName = "deviceCode")]
public string DeviceCode { get; set; }
[XmlAttribute(AttributeName = "measurement")]
public string Measurement { get; set; }
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "schemaLocation", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string SchemaLocation { get; set; }
}
Then you could use a serializer :
public class Serializer
{
public T Deserialize<T>(string input) where T : class
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (StringReader stringReader = new StringReader(input))
{
return (T)xmlSerializer.Deserialize(stringReader);
}
}
public string Serialize<T>(T ObjectToSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(ObjectToSerialize.GetType());
StringBuilder builder = new StringBuilder();
using (StringWriterWithEncoding textWriter = new StringWriterWithEncoding(builder, Encoding.UTF8))
{
xmlSerializer.Serialize(textWriter, ObjectToSerialize);
return textWriter.ToString();
}
}
}
public class StringWriterWithEncoding : StringWriter
{
Encoding encoding;
public StringWriterWithEncoding(StringBuilder builder, Encoding encoding)
: base(builder)
{
this.encoding = encoding;
}
public override Encoding Encoding
{
get { return encoding; }
}
}
And finally you can access whatever you want by doing the following :
var serializer = new Serializer();
//I used a local file for testing, but it should be the same thing with your file
var xmlInputData = File.ReadAllText(#"MyXmlPath");
var output = serializer.Deserialize<Ticketlayout >(xmlInputData);
var logicalName = output.LogicalName;

DataContractSerializer - Namespace issues

I'm using HttpClient to post xml to a rest service. The problem is the service expects namespace prefix's in fashion that I'm unable to achieve with DataContractSerializer.
Expected xml:
<gto:createRequest xmlns:gto="http://www...com/sign">
<userId></userId>
<visibleDataContentType></visibleDataContentType>
<visibleData></visibleData>
<hiddenData></hiddenData>
<expiryInSeconds></expiryInSeconds>
</gto:createRequest>
The object i'm serialzing:
namespace ABC
{
[DataContract(Name = "createRequest", Namespace = "http://www...com/sign")]
public class CreateRequest
{
[DataMember(Name = "userId")]
public string UserId { get; set; }
[DataMember(Name = "visibleDataContentType")]
public string VisibleDataContentType { get; set; }
[DataMember(Name = "visibleData")]
public string VisibleData { get; set; }
[DataMember(Name = "hiddenData")]
public string HiddenData { get; set; }
[DataMember(Name = "expiryInSeconds")]
public int ExpiryInSeconds { get; set; }
}
}
I can't get the prefix "gto: createRequest", this what DataContractSerializer outputs:
<createRequest xmlns="http://www...com/sign" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<expiryInSeconds></expiryInSeconds>
<hiddenData></hiddenData>
<userId></userId>
<visibleData></visibleData>
<visibleDataContentType></visibleDataContentType>
</createRequest>
I have tried the old XmlSerializer but with no luck. Any ideas!?
Update: The namespace prefix does not have to be gto: but i has to be there!
Update: the output from Ondrej Svejdars answer that the server doesn't accept:
<gto:createRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:gto="http://www.test.com/sign">
<gto:expiryInSeconds>60</gto:expiryInSeconds>
<gto:hiddenData>hidden</gto:hiddenData>
<gto:userId>123456</gto:userId>
<gto:visibleData>visible</gto:visibleData>
<gto:visibleDataContentType>text/plain</gto:visibleDataContentType>
</gto:createRequest>
[Edited to match the gto: only on top element]
You can tweak xml writer:
public class XmlProxyWritter : XmlTextWriter {
private string m_NS;
public XmlProxyWritter(string ns, TextWriter w)
: base(w) {
m_NS = ns;
}
public XmlProxyWritter(string ns, Stream w, Encoding encoding)
: base(w, encoding) {
m_NS = ns;
}
public XmlProxyWritter(string ns, string filename, Encoding encoding)
: base(filename, encoding) {
m_NS = ns;
}
public override string LookupPrefix(string ns) {
if (string.Compare(ns, m_NS, StringComparison.OrdinalIgnoreCase) == 0) {
return "gto";
}
return base.LookupPrefix(ns);
}
public override void WriteStartElement(string prefix, string localName, string ns) {
if (string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(ns)) {
prefix = LookupPrefix(ns);
}
base.WriteStartElement(prefix, localName, ns);
}
}
Business class:
[XmlRoot(ElementName = "createRequest", Namespace = "http://www.test.com/sign")]
public class CreateRequest {
[XmlElement(ElementName="userId", Namespace = "")]
public string UserId { get; set; }
[XmlElement(ElementName = "visibleDataContentType", Namespace = "")]
public string VisibleDataContentType { get; set; }
[XmlElement(ElementName = "visibleData", Namespace = "")]
public string VisibleData { get; set; }
[XmlElement(ElementName = "hiddenData", Namespace = "")]
public string HiddenData { get; set; }
[XmlElement(ElementName = "expiryInSeconds", Namespace = "")]
public int ExpiryInSeconds { get; set; }
}
Call example (where http://www.test.com/sign is the namespace of CreateRequest)
string result;
var serXml = new XmlSerializer(typeof(CreateRequest));
using (var stream = new MemoryStream()) {
using (var writer = new XmlProxyWritter("http://www.test.com/sign", stream, Encoding.UTF8)) {
serXml.Serialize(writer, new CreateRequest {
ExpiryInSeconds = 1,
HiddenData = "my preasures",
UserId = "Pepa"
});
}
result = Encoding.UTF8.GetString(stream.ToArray());
}
Output:
<?xml version="1.0" encoding="utf-8"?>
<gto:createRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gto="http://www.test.com/sign">
<userId>Pepa</userId>
<hiddenData>my preasures</hiddenData>
<expiryInSeconds>1</expiryInSeconds>
</gto:createRequest>
Which works for you (I hope) but it feels like kind of hacking; maybe the proper solution here would be to "teach" server the correct xml format ? :)

Categories