I have xml like this:
<data>
<audit_values>
<audit_value>
<channel>2</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
<audit_value>
<channel>1</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
</audit_values>
</data>
And I need to deserialize it to class. But the problem is, that week will be change in future(it will be contains more elements, and name of them I dont know)
Data:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
AuditValue:
[XmlRoot("audit_value")]
public class AuditValue
{
[XmlElement("week", typeof(TVR))]
public Week Week;
}
Week:
[XmlRoot("week")]
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read();
var sub = reader.ReadSubtree();
do
{
if (sub.NodeType == XmlNodeType.Element)
{
string name = sub.Name;
string val = sub.ReadElementContentAsString();
Values.Add(name, Convert.ToDouble(val));
}
} while (sub.Read());
}
public void WriteXml(XmlWriter writer)
{
}
}
But after deserialization I have only one element with only one recored in dictionary Values. What I'm doing wrong?
GitHub:
https://github.com/olegletynain/XmlTest/tree/master/XmlTestRead
I tweaked your ReadXml method based on #Matthew Whited's idea in the comments section and the following method does the job:
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
As a side note you only need XmlRoot on the actual root element not on every class so I removed it from AuditValue and Week. Also I don't know what TVR is. It didn't compile with "typeof(TVR)" so I removed it as well.
For the sake of completeness here's my version of the classes:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
public class AuditValue
{
[XmlElement("week")]
public Week Week;
}
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
public void WriteXml(XmlWriter writer)
{
}
}
You should consider using the DataContractSerializer instead of XmlSerializer and implement the IExtensibleDataObject interface on your DataContract class.
Implementing IExtensibleDataObject allows the DataContract class to persist unknown information in the ExtensionData field, which prevents it from being lost if the XML being deserialized contains unknown elements, and then is re-serialized and saved.
Your Audit class would look something like this:
[DataContract(Name = "audit_value", Namespace = "")]
public class AuditValue : IExtensibleDataObject
{
[DataMember(Name = "channel")]
public int Channel { get; set; }
[DataMember(Name = "week")]
public Week Week { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "week", Namespace = "")]
public class Week : IExtensibleDataObject
{
[DataMember(Name = "mo_th")]
public Decimal MondayThroughThursday { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
You'll still need to update your code to deserialize the additional elements as POCOs, but at least the underlying data will be persisted until you get around to it.
Try this. XmlElement eliminates a lay of tags which you don't have. You have an extra 's' at end of values.
From
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
To
[XmlElement("audit_value")]
public AuditValue[] AuditValues { get; set; }
The error was in ReadXml method, now I changed it to this:
public void ReadXml(XmlReader reader)
{
reader.Read();
do
{
if (!reader.IsEmptyElement)
{
var name = reader.Name;
var val = Convert.ToDouble(reader.ReadElementContentAsString());
Values.Add(name, val);
}
else
{
reader.Skip();
}
} while (reader.Name != "week");
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
}
}
And I works fine. This Solution without using XElement and Linq, #Volkan Paksoy offered method with XElement which is easier to understand
Related
Is it possible to store the original XML element in a C# class, for example?
Original XML:
<data someattributea="" someattributeb="" someattributec="" />
C#
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
namespace Xml2CSharp
{
[XmlRoot(ElementName="data")]
public class Data {
[XmlAttribute(AttributeName="someattributea")]
public string Someattributea { get; set; }
[XmlAttribute(AttributeName="someattributeb")]
public string Someattributeb { get; set; }
[XmlAttribute(AttributeName="someattributec")]
public string Someattributec { get; set; }
public sourceXML { get; set; } //this would return <data someattributea="" someattributeb="" someattributec="" />
}
}
I understand I could deserialize the class again but some XML objects are unknown at design time.
If you really need to capture everything about the <data /> element including the element name and namespace itself into a string literal, you will need to implement IXmlSerializable and serialize your Data type manually. For instance, here is a prototype implementation:
[XmlRoot(ElementName = ElementName)]
public class Data : IXmlSerializable
{
public const string ElementName = "data";
XElement element = new XElement((XName)ElementName);
public string Someattributea
{
get { return (string)element.Attribute("someattributea"); }
set { element.SetAttribute("someattributea", value); }
}
public string Someattributeb
{
get { return (string)element.Attribute("someattributeb"); }
set { element.SetAttribute("someattributeb", value); }
}
public string Someattributec
{
get { return (string)element.Attribute("someattributec"); }
set { element.SetAttribute("someattributec", value); }
}
public string SourceXML
{
get
{
return element.ToString();
}
set
{
if (value == null)
throw new ArgumentNullException();
element = XElement.Parse(value);
}
}
#region IXmlSerializable Members
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
element = (XElement)XNode.ReadFrom(reader);
}
public void WriteXml(XmlWriter writer)
{
foreach (var attr in element.Attributes())
writer.WriteAttributeString(attr.Name.LocalName, attr.Name.NamespaceName, attr.Value);
foreach (var child in element.Elements())
child.WriteTo(writer);
}
#endregion
}
public static class XElementExtensions
{
public static void SetAttribute(this XElement element, XName attributeName, string value)
{
var attr = element.Attribute(attributeName);
if (value == null)
{
if (attr != null)
attr.Remove();
}
else
{
if (attr == null)
element.Add(new XAttribute(attributeName, value));
else
attr.Value = value;
}
}
}
Notes:
When reading, the complete XML is loaded into an XElement member which can be queried using LINQ to XML. As a result the original formatting may get lost.
IXmlSerializable is tricky to implement correctly. See Proper way to implement IXmlSerializable? and How to Implement IXmlSerializable Correctly for some tips on how to do it.
The known properties Someattributea, Someattributeb and Someattributec now become surrogate lookups into the underlying XElement.
Working .Net fiddle here.
If, on the other hand, you only need to capture unknown elements, attributes and text content, you can use [XmlAnyAttribute], [XmlAnyElement] and [XmlText] (the first two of which are suggested in this answer to XmlSerializer equivalent of IExtensibleDataObject by Marc Gravell). This approach results in a much simpler version of Data:
[XmlRoot(ElementName = "data")]
public class Data
{
[XmlAttribute(AttributeName = "someattributea")]
public string Someattributea { get; set; }
[XmlAttribute(AttributeName = "someattributeb")]
public string Someattributeb { get; set; }
[XmlAttribute(AttributeName = "someattributec")]
public string Someattributec { get; set; }
[XmlAnyAttribute]
public XmlAttribute[] Attributes { get; set; }
[XmlAnyElement]
[XmlText] // Captures mixed content at the root level as well as child elements.
public XmlNode[] ChildNodes { get; set; }
}
Working .Net fiddle #2 here.
I have two classes, "company" derived from Treenode, and "document".
[Serializable]
[XmlRoot("Company")]
public class Company : TreeNode, IXmlSerializable
{
private string _x;
private string _y;
public Company() { }
[XmlElement("X")]
public string X { get; set; }
[XmlElement("Y")]
public string Y { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "Company")
{
x = reader["X"].ToString;
y = reader["Y"].ToString;
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("X", this.X.ToString());
writer.WriteElementString("Y", this.Y.ToString());
}
}
public class Document
{
private int _id;
private string _name;
private Company _company;
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company")]
public Company Comp { get; set; }
}
When I try to serialize document, then save to file, its work. But when I deserialize, reader parameter always null. Any solutions ?
This is my code for deserialize. "sr" is variable that hold xml text.
var sr = new StreamReader(ms);
var myStr = sr.ReadToEnd();
XmlSerializer serializer = new XmlSerializer(typeof(List<Document>));
using (TextReader tr = new StringReader(myStr))
{
List<Document> docu = (List<Document>)serializer.Deserialize(tr);
}
I try to implement ISerialization and debug but never fired, and try to overide serialize and deserialize method, and no luck.
I am using .NET Framework 3.5
As explained in this article, implementing IXmlSerializable correctly is in fact quite tricky. Your implementation of ReadXml() appears to violate the requirement that it consume the wrapper element itself as well as all the contents, like so:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.MoveToContent();
// Read attributes
Boolean isEmptyElement = reader.IsEmptyElement; // (1)
reader.ReadStartElement();
if (!isEmptyElement) // (1)
{
// Read Child elements X and Y
// Consume the end of the wrapper element
reader.ReadEndElement();
}
}
Furthermore, reader["X"] returns the value of the XML attribute named "X", as explained in the docs. In your WriteXml() you wrote the values of X and Y as nested XML elements. This explains the NullReferenceException. You need to fix your read and write methods to be consistent.
However, I'd suggest an alternative to implementing IXmlSerializable, which is to introduce a surrogate type for your Company. First, extract all the non-TreeNode properties of Company into an interface:
public interface ICompany
{
string X { get; set; }
string Y { get; set; }
}
public class Company : TreeNode, ICompany
{
public Company() { }
public string X { get; set; }
public string Y { get; set; }
}
This is optional but makes the code clearer. Next, introduce a surrogate POCO that implements the same interface but does not inherit from TreeNode:
public class CompanySurrogate : ICompany
{
public string X { get; set; }
public string Y { get; set; }
public static implicit operator CompanySurrogate(Company company)
{
if (company == null)
return null;
// For more complex types, use AutoMapper
return new CompanySurrogate { X = company.X, Y = company.Y };
}
public static implicit operator Company(CompanySurrogate surrogate)
{
if (surrogate == null)
return null;
// For more complex types, use AutoMapper
return new Company { X = surrogate.X, Y = surrogate.Y };
}
}
Notice that the surrogate can be implicitly converted to your original Company type? Now you can use the surrogate in XML serialization by setting the XmlElementAttribute.Type attribute property to be that of the surrogate:
public class Document
{
public Document() { }
[XmlElement("ID")]
public int ID { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Company", Type = typeof(CompanySurrogate))]
public Company Comp { get; set; }
}
This avoids all possibilities of error in implementing IXmlSerializable. Given the following input list:
var list = new List<Document>
{
new Document { Name = "my name", ID = 101, Comp = new Company { X = "foo", Y = "bar", NodeFont = new System.Drawing.Font("Arial", 10) } },
new Document { Name = "2nd name", ID = 222, Comp = new Company { X = "tlon", Y = "ukbar" } },
};
The following XML can will be generated, and can be deserialized successfully:
<ArrayOfDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Document>
<ID>101</ID>
<Name>my name</Name>
<Company>
<X>foo</X>
<Y>bar</Y>
</Company>
</Document>
<Document>
<ID>222</ID>
<Name>2nd name</Name>
<Company>
<X>tlon</X>
<Y>ukbar</Y>
</Company>
</Document>
</ArrayOfDocument>
That being said, I don't really recommend this design. Your UI should present your data model, it should not be your data model. See for instance How does one implement UI independent applications?. Replacing Company with ICompany whenever possible could be a first step to changing your design; you may find it becomes easier to replace your existing architecture with a TreeNode that looks like this:
public class CompanyNode : TreeNode
{
public ICompany Company { get; set; }
}
I have a weird XML setup here: I need to interface with a third-party and their XML layout that they're using is beyond my control - no chance to changing anything...
In the scope of a larger XML, I find myself needing to serialize (and later also deserialize) a list of items (for a mobile device) which are defined as having a Type and a Number property (both strings). So this would be something like:
public class SerialNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
And this would normally serialize a List<SerialNumber> SerialNumbers as
<SerialNumbers>
<SerialNumber>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
</SerialNumber>
<SerialNumber>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumber>
</SerialNumbers>
However, in my case, I need to send this XML:
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
So basically, the list elements need to be omitted - I just need a container <SerialNumbers> and then for each entry in the list, I only need to serialize out the Type and Number subelements.
How can I do this easily in .NET with the XmlSerializer ?
I tried to use
[XmlRoot(ElementName="")]
public class SerialNumber
or
[XmlArray]
[XmlArrayItem(ElementName = "")]
public List<SerialNumber> SerialNumbers { get; set; }
but neither of these worked - I still get my full serialization with the <SerialNumber> elements inside the <SerialNumbers> container...
Is there an easy trick to achieve what I'm looking for? I'd much rather not go low-level and start concetanating together my XML manually....
Thanks!
You could use custom serialization with IXmlSerializable.
public class SerialNumbers : List<SerialNumber>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Clear();
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
var serialNumber = new SerialNumber
{
Type = reader.ReadElementContentAsString("Type", ""),
Number = reader.ReadElementContentAsString("Number", "")
};
Add(serialNumber);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
foreach (var serialNumber in this)
{
writer.WriteElementString("Type", serialNumber.Type);
writer.WriteElementString("Number", serialNumber.Number);
}
}
}
public class SerialNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
Example:
var ser = new XmlSerializer(typeof(SerialNumbers));
var reader = new StringReader(#"
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
".Trim());
var result = (SerialNumbers) ser.Deserialize(reader);
var writer = new StringWriter();
ser.Serialize(writer, result);
Result:
<?xml version="1.0" encoding="utf-16"?>
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
This might do the trick for you
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class SerialNumbers
{
private string[] itemsField;
private ItemsChoiceType[] itemsElementNameField;
[System.Xml.Serialization.XmlElementAttribute("Number", typeof(string))]
[System.Xml.Serialization.XmlElementAttribute("Type", typeof(string))]
[System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemsElementName")]
public string[] Items
{
get
{
return this.itemsField;
}
set
{
this.itemsField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute("ItemsElementName")]
[System.Xml.Serialization.XmlIgnoreAttribute()]
public ItemsChoiceType[] ItemsElementName
{
get
{
return this.itemsElementNameField;
}
set
{
this.itemsElementNameField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(IncludeInSchema = false)]
public enum ItemsChoiceType
{
Number,
Type,
}
I have a XML file:
<Hand cards="C5,SQ,DQ,H8,C9,H7,S9,D5,DA,CJ,S6,HK,D4">
</Hand>
I define a class
[Serializable()]
[XmlRoot("Hand")]
public class Hand
{
[XmlAttribute("cards")]
public List<string> Cards{get;set;}
}
How to deserialize a XML to object in this case? Hand object result must have Cards = {C5,SQ,DQ,H8,C9,H7,S9,D5,DA,CJ,S6,HK,D4}.
You cannot.
What you can do is to create a property which will do this conversion in its getter/setter
[XmlIgnore]
public List<string> CardList { get; private set; }
[XmlAttribute("cards")]
public string Cards {
get { return String.Join(",", CardList); }
set { CardList = value.Split(",").ToList(); }
}
You can do this with the help of IXmlSerializable. Read more about it on MSDN.
This way
[Serializable()]
[XmlRoot("Hand")]
public class Hand : IXmlSerializable {
[XmlAttribute("cards")]
public List<string> Cards { get; set; }
public System.Xml.Schema.XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader) {
this.Cards = new List<string>(reader.GetAttribute("cards").Split(','));
}
public void WriteXml(XmlWriter writer) {
writer.WriteAttributeString("cards",
string.Join(",",
this.Cards != null ? this.Cards : new List<string>()));
}
}
Hope this helps you.
I have an xml file looking somewhat like this:
<xml>
<A>value</A>
<B>value</B>
<listitems>
<item>
<C>value</C>
<D>value</D>
</item>
</listitems>
</xml>
And I have a two objects representing this xml:
class XmlObject
{
public string A { get; set; }
public string B { get; set; }
List<Item> listitems { get; set; }
}
class Item : IXmlSerializable
{
public string C { get; set; }
public string D { get; set; }
//Implemented IXmlSerializeable read/write
public void ReadXml(System.Xml.XmlReader reader)
{
this.C = reader.ReadElementString();
this.D = reader.ReadElementString();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("C", this.C);
writer.WriteElementString("D", this.D);
}
}
I use the XmlSerializer to serialize/deserialize the XmlObject to file.
The problem is that when I implemented the custom IXmlSerializable functions on my "sub-object" Item I always only get one item(the first) in my XmlObject.listitems collection when deserializing the file.
If I remove the : IXmlSerializable everything works as expected.
What do I do wrong?
Edit: I have have implemented IXmlSerializable.GetSchema and I need to use IXmlSerializable on my "child-object" for doing some custom value transformation.
Modify your code like this:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
this.C = reader.ReadElementString();
this.D = reader.ReadElementString();
reader.Read();
}
First you skip the start of the Item node, read the two strings, then read past the end node so the reader is at the correct place. This will read all nodes in the array.
You need to pay attention when modifying xml yourself :)
You don't need to use IXmlSerializable. But if you want you should implement GetShema() method. After some modification code that works looks like that:
[XmlRoot("XmlObject")]
public class XmlObject
{
[XmlElement("A")]
public string A { get; set; }
[XmlElement("B")]
public string B { get; set; }
[XmlElement("listitems")]
public List<Item> listitems { get; set; }
}
public class Item : IXmlSerializable
{
[XmlElement("C")]
public string C { get; set; }
[XmlElement("D")]
public string D { get; set; }
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(System.Xml.XmlReader reader)
{
this.C = reader.ReadElementString();
this.D = reader.ReadElementString();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("C", this.C);
writer.WriteElementString("D", this.D);
}
#endregion
}
Results for 2 items in itemlist will look like that:
<?xml version="1.0" encoding="utf-8"?>
<XmlObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<A>value</A>
<B>value</B>
<listitems>
<C>value0</C>
<D>value0</D>
</listitems>
<listitems>
<C>value1</C>
<D>value1</D>
</listitems>
</XmlObject>