Deserialize xml which uses attribute name/value pairs - c#

My application receives a constant stream of xml files which are more or less a direct copy of the database record
<record type="update">
<field name="id">987654321</field>
<field name="user_id">4321</field>
<field name="updated">2011-11-24 13:43:23</field>
</record>
And I need to deserialize this into a class which provides nullable property's for all columns
class Record {
public long? Id { get; set; }
public long? UserId { get; set; }
public DateTime? Updated { get; set; }
}
I just cant seem to work out a method of doing this without having to parse the xml file manually and switch on the field's name attribute to store the values.
Is their a way this can be achieved quickly using an XmlSerializer? And if not is their a more efficient way of parsing it manually?
Regards and thanks
My main problem is that the attribute name needs to have its value set to a property name and its value as the contents of a <field>..</field> element

Edit
Edited the answer to reflect the best you can get with your current xml layout you will get an array of field objects with two properties, Name, eg ID, and Value, the text in the node
You can create an Xml Definition class, pass this to an Xml Serializer and it will return you the object with the values of your xml intialised into it's properties.
So given the following definition file.
[XmlTypeAttribute]
[XmlRootAttribute("record")]
public class RecordXmlConfiguration
{
[XmlElementAttribute("field")]
public Field[] Fields { get; set; }
}
[XmlTypeAttribute]
public class Field
{
[XmlAttributeAttribute("name")]
public string Name { get; set; }
[XmlText]
public string Value { get; set; }
}
Then when you have your xml you pass it to a method and it should return you an object of the type of your Xml defintiion class as so
public static object Deserialize(string xml)
{
var deserializer = new System.Xml.Serialization.XmlSerializer(typeof(RecordXmlConfiguration));
using (var reader = XmlReader.Create(new StringReader(xml)))
{
return (RecordXmlConfiguration)deserializer.Deserialize(reader);
}
}
It can be fiddly, but when its set up right it's saved me tonnes of time, as when your done with it you can also create a serialize method in the same maanner to return it to it's Xml form.
make sure to add references to your project to allow for these using statements
using System.Xml.Serialization;
using System.Xml;

Related

IXmlSerializable ReadXml implementation

I am trying to get the name of an XML tag into a class property when performing XML deserialization. I need the name as a property since multiple XML tags share the same class. The XML and associated classes are defined below.
I have an XML response which I receive in the format:
<Data totalExecutionTime="00:00:00.0467241">
<ItemNumber id="1234" order="0" createdDate="2017-03-24T12:07:09.07" modifiedDate="2018-08-29T16:59:19.127">
<Value modifiedDate="2017-03-24T12:07:12.77">ABC1234</Value>
<Category id="5432" parentID="9876" itemOrder="0" modifiedDate="2017-03-24T12:16:23.687">The best category</Category>
... <!-- like 100 other elements -->
</ItemNumber>
</Data>
Deserialize done as follows:
XmlSerializer serializer = new XmlSerializer(typeof(ItemData));
using (TextReader reader = new StringReader(response))
{
ItemData itemData = (ItemData)serializer.Deserialize(reader);
}
And a class for the top level, ItemData:
[Serializable]
[XmlRoot("Data")]
public class ItemData
{
[XmlAttribute("totalExecutionTime")]
public string ExecutionTime { get; set; }
[XmlElement("ItemNumber", Type = typeof(ItemBase))]
public List<ItemBase> Items { get; set; }
}
ItemBase is defined as:
[Serializable]
public class ItemBase
{
[XmlElement("Value")]
public virtual ItemProperty ItemNumber { get; set; } = ItemProperty.Empty;
[XmlElement("ItemName")]
public virtual ItemProperty Category { get; set; } = ItemProperty.Empty;
... // like 100 other properties
}
And finally ItemProperty:
public class ItemProperty : IXmlSerializable
{
public static ItemProperty Empty { get; } = new ItemProperty();
public ItemProperty()
{
this.Name = string.Empty;
this.Value = string.Empty;
this.Id = 0;
this.Order = 0;
}
public string Name { get; set; }
[XmlText] // no effect while using IXmlSerializable
public string Value { get; set; }
[XmlAttribute("id")] // no effect while using IXmlSerializable
public int Id { get; set; }
[XmlAttribute("itemOrder")] // no effect while using IXmlSerializable
public int Order { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
string name = reader.Name;
this.Name = name;
string val = reader.ReadElementString();
this.Value = val;
if (reader.HasAttributes)
{
string id = reader.GetAttribute("id");
this.Id = Convert.ToInt32(id);
string itemOrder = reader.GetAttribute("itemOrder");
this.Order = Convert.ToInt32(itemOrder);
string sequence = reader.GetAttribute("seq");
this.Sequence = Convert.ToInt32(sequence);
}
// it seems the reader doesn't advance to the next element after reading
if (reader.NodeType == XmlNodeType.EndElement && !reader.IsEmptyElement)
{
reader.Read();
}
}
public void WriteXml(XmlWriter writer)
{
throw new NotImplementedException();
}
}
The point of implementing the IXmlSerializable interface is because ultimately I need the name of the XML tag that is stored as an ItemProperty and that information is not captured when using the XML class/property attributes. I believe this is the case since the attributes determine which class to use for the deserialization and normally each XML tag would have an associated class. I don't want to go that direction since there are such a large number of different tags that may be in the response and they all share similar attributes. Hence the ItemProperty class.
I've tried also passing the name of the tag via a parameterized constructor in the ItemProperty and setting the name property there, but when deserialization is performed, it uses the default constructor and then sets the property values, so that is not an option.
Reflection doesn't work either since the class is always ItemProperty and therefore doesn't have a unique name.
Maybe how I'm structuring the XML attributes could be done differently to achieve what I'm trying to do, but I don't see it.
I'm open to any way to solve this problem, but I'm pretty sure it entails implementing IXmlSerializable.ReadXml(). I know it is the job of the XmlReader to read the entirety of the XML and advance the reader to the end of the text, but I'm a little unclear on how to do that.
TLDR: How do I properly implement IXmlSerializable.ReadXml() while capturing the XML tag name, tag value, and all attributes into the class properties?
Edit: with the updated ReadXml method, I get all the data needed at the ItemProperty level, but class ItemData, the Items list only ever has one item. I assume because I am not advancing the reader properly.
From the documentation for IXmlSerializable.ReadXml(XmlReader):
When this method is called, the reader is positioned on the start tag that wraps the information for your type. ... When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.
Your ReadXml() can be modified to meet these requirements as follows:
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
this.Name = reader.LocalName; // Do not include the prefix (if present) in the Name.
if (reader.HasAttributes)
{
var id = reader.GetAttribute("id");
if (id != null)
// Since id is missing from some elements you might want to make it nullable
this.Id = XmlConvert.ToInt32(id);
var order = reader.GetAttribute("itemOrder");
if (order != null)
// Since itemOrder is missing from some elements you might want to make it nullable
this.Order = XmlConvert.ToInt32(order);
string sequence = reader.GetAttribute("seq");
//There is no Sequence property?
//this.Sequence = Convert.ToInt32(sequence);
}
// Read element value.
// This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
// thus there is no need for an additional Read()
this.Value = reader.ReadElementContentAsString();
}
Notes:
You are calling ReadElementString() whose documentation states:
We recommend that you use the ReadElementContentAsString() method to read a text element.
As suggested, I modified your ReadXml() to use this method. In turn, its documentation states:
This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
Thus this method should leave the XmlReader positioned exactly as required by ReadXml(), ensuring the reader is advanced properly.
The XML attributes of each ItemProperty element must be processed before that element's content is read, since reading the content advances the reader past the element start -- and its attributes.
Utilities from the XmlConvert class should be used to parse and format XML primitives so that numerical and date/time values are not erroneously localized.
You probably don't want to include the namespace prefix (if any) in the Name property.
Demo fiddle here.

Get tags names from json file as a variable in a class

I am using C# MVC
I am importing a Json file from a location to an Rest API. In order to display the data. the program needs to know the name of the tags.
I would like to know if there is a way to get the tags from the files in an array of strings and declare them via program instead of manually inputting them.
Current C# Code
public class Person
{
public int Id { get; set; }
public int SomeID { get; set; }
public int Number { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Current Json File:
[{"Id":212,"SomeID":71,"Number":0,"Name":"Mr Jones Mones","Address":"21, street, city"}
So as mentioned this code words fine. But, as I am importing data, the tags may contain different information and I would like to have the public declaration in a way that I can import any file to the API and be able to edit and export it.
Thank you
If you use JSON.net you can parse out json into a JObject then iterate over it's properties like so, however each property value can be any type of object so you'd need to write something to either recursively listed out nested objects or check if values are strings and only take them if they are etc.
var obj = JObject.Parse("{ \"test\": \"test\", \"test1\": \"test1\", \"test2\": \"test2\" }");
foreach (var prop in obj.Properties())
{
Console.WriteLine(prop.Name);
}
You can use JsonConvert like this:
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
Read the docs for JsonConvert for more info.

Generating XML document with C#

I need to generate an XML document that follows this specifictaion
<productName locale="en_GB">Name</productName>
but using XMLSeralization I am getting the following
<productName locale="en_GB">
<Name>Name</Name>
</productName>
My C# code is like this:
[Serializable]
public class productName
{
public productName()
{
}
public string Name;
[XmlAttribute]
public string locale;
}
XmlAttribute is what is required to show the locale in the correct place, but I am unable to figure out how to export the Name field correctly.
Does anyone have an idea?
Thanks
EDIT:
This is the code to generate the XML
public static class XMLSerialize
{
public static void SerializeToXml<T>(string file, T value)
{
var serializer = new XmlSerializer(typeof(T));
using (var writer = XmlWriter.Create(file))
serializer.Serialize(writer, value);
}
public static T DeserializeFromXML<T>(string file)
{
XmlSerializer deserializer = new XmlSerializer(typeof(T));
TextReader textReader = new StreamReader(file);
T result;
result = (T)deserializer.Deserialize(textReader);
textReader.Close();
return result;
}
}
Instead of specifying Name as element specify it as text value by adding [XmlText] attribute
[XmlText]
public string Value { get; set; }
This contains not only a direct answer to your question, but more of a indirect answer of how to solve similar issues like this in the future.
Start the other way around, with your xml, write your xml exactly like you want it and go from there, like this:
// assuming data.xml contains the xml as you'd like it
> xsd.exe data.xml // will generate data.xsd, ie xsd-descriptor
> xsd.exe data.xsd /classes // will generate data.cs, ie c# classes
> notepad.exe data.cs // have a look at data.cs with your favorite editor
Now just have a look at data.cs, this will contain an enormous amount of attributes and stuff and the namespaces are probably wrong, but at least you know how to solve your particular xml-issue.
The direct answer is to use the XmlTextAttribute on the given property, preferably named Value since that is the convention I've seen so far.
[Serializable]
public class productName {
public productName() { }
[XmlText]
public string Value {get; set;}
[XmlAttribute]
public string locale {get; set;}
}

How to Deserialize XML elements to generic list with unknown element names

I am retrieving and successfully deserializing an xml string from a database table column for the known element names (these do not change) but there are also some nested XML Elements called "Other Attributes" which are not always going to be known. I'm having some trouble deserialising these unknown element names to a generic list so I can display them in html once deserialised.
The XML is as follows:
<Detail>
<DetailAttributes>
<Name>Name_123</Name>
<Type>Type_123</Type>
</DetailAttributes>
<OtherAttributes>
<SummaryKey AttributeName="SummaryKey">SummaryKey_123</SummaryKey>
<Account AttributeName="Account">Account_123</Account>
</OtherAttributes>
</Detail>
I have no problem deserialising the 'Name' and 'Type' elements and I can deserialise the 'SummaryKey' and 'Account' elements but only if I explicitly specify their element names - which is not the desired approach because the 'OtherAttributes' are subject to change.
My classes are as follows:
[XmlRoot("Detail")]
public class objectDetailsList
{
[XmlElement("DetailAttributes"), Type = typeof(DetailAttribute))]
public DetailAttribute[] detailAttributes { get; set; }
[XmlElement("OtherAttributes")]
public List<OtherAttribute> otherAttributes { get; set; }
public objectDetailsList()
{
}
}
[Serializable]
public class Detail Attribute
{
[XmlElement("Type")]
public string Type { get;set; }
[XmlElement("Name")]
public string Name { get;set; }
public DetailAttribute()
{
}
}
[Serializable]
public class OtherAttribute
{
//The following will deserialise ok
//[XmlElement("SummaryKey")]
//public string sumKey { get; set; }
//[XmlElement("Account")]
//public string acc { get; set; }
//What I want to do below is create a list of all 'other attributes' without known names
[XmlArray("OtherAttributes")]
public List<Element> element { get; set; }
}
[XmlRoot("OtherAttributes")]
public class Element
{
[XmlAttribute("AttributeName")]
public string aName { get; set; }
[XmlText]
public string aValue { get; set; }
}
When I try to retrieve the deserialised list of OtherAttribute elements the count is zero so it's not able to access the elements nested within "Other Attributes".
Can anybody help me with this please?
With concrete classes and dynamic data like this, you won't be able to lean on the standard XmlSerializer to serialize / deserialize for you - as it reflects on your classes, and the properties you want to populate simply don't exist. You could provide a class with all possible properties if your set of 'OtherAttributes' is known and finite, and not subject to future change, but that would give you an ugly bloated class (and I think you've already decided this is not the solution).
Practical options therefore:
Do it manually. Use the XmlDocument class, load your data with .Load(), and iterate the nodes using .SelectNodes() using an XPath query (something like "/Detail/OtherAttributes/*"). You will have to write the lot yourself, but this gives you complete control over the serialization / deserialization. You won't have to cover your code in (arguably superfluous!) attributes either.
Use Json.NET (http://james.newtonking.com/json), it allows for far greater control over serialization and deserialization. It's fast, has good docs and is overall pretty nifty really.

how to serialize class objects to xml document

I hope to find a solution from you. What I need is to serialize ValidatorList class object into an xml document. How to do this?
I tried like this:
XmlSerializer _serializer = new XmlSerializer(list);
But I don't know how to make output of xml for list that has several classes.
C#
_list= new ListVal();
Type _type = typeof(ras);
_list.Add(new RequiredField
{
Property = _type.GetProperty("CustRef")
}.Add(new AsciiVal()));
_list.Add(new RequiredField
{
Property = _type.GetProperty("ctr")
}.Add(new StringLengthVal
{
min= 3,
max= 3
}));
[Serializable]
public class Field
{
public Field Next
{
get;
set;
}
public Field TypeName
{
get;
set;
}
public Field PropertyName
{
get;
set;
}
}
public class RequiredField : Field
{
//TODO
}
public class AsciiVal: Field
{
//TODO
}
public class StringLengthVal: Field
{
//TODO
}
public class ListVal: List<Field>
{
//TODO
}
I have something close, but not exactly the Xml you want. In actual fact I think you'll see that the Xml produced below makes a bit more sense than what you have.
To get you started, you control the serialization and deserialization using attributes in the System.Xml.Serialization namespace. A few useful ones to read up on are
XmlRootAttribute
XmlElementAttribute
XmlAttributeAttribute
XmlIncludeAttribute
So I mocked up some code which closely matches your own. Notice the addition of some attributes to instruct the serializer how I want the Xml to be laid out.
[XmlInclude(typeof(AsciiValidator))]
[XmlInclude(typeof(RequiredValidator))]
[XmlInclude(typeof(StringLengthValidator))]
public class FieldValidator
{
[XmlElement("Next")]
public FieldValidator Next
{
get;
set;
}
[XmlElement("PropertyName")]
public string PropertyName
{
get;
set;
}
}
public class AsciiValidator: FieldValidator
{
}
public class RequiredValidator: FieldValidator
{
}
public class StringLengthValidator: FieldValidator
{
[XmlElement]
public int MinLength{get;set;}
[XmlElement]
public int MaxLength{get;set;}
}
[XmlRoot("ValidatorList")]
public class ValidatorList : List<FieldValidator>
{
}
Point of interest; Every class inheriting FieldValidator must be added to the list of known types using XmlIncludeAttribute so the serializer knows what to do with them)
Then I created an example object map:
var test = new ValidatorList();
test.Add(
new RequiredValidator()
{
PropertyName="CustRef",
Next = new AsciiValidator()
});
test.Add(
new RequiredValidator()
{
PropertyName="CurrencyIndicator",
Next = new StringLengthValidator(){
MinLength=3,
MaxLength = 10
}
});
Finally I told the serializer to serialize it (and output the result to the console)
var ser = new XmlSerializer(typeof(ValidatorList));
ser.Serialize(Console.Out,test);
This was the result:
<?xml version="1.0" encoding="utf-8"?>
<ValidatorList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FieldValidator xsi:type="RequiredValidator">
<Next xsi:type="AsciiValidator" />
<PropertyName>CustRef</PropertyName>
</FieldValidator>
<FieldValidator xsi:type="RequiredValidator">
<Next xsi:type="StringLengthValidator">
<MinLength>3</MinLength>
<MaxLength>10</MaxLength>
</Next>
<PropertyName>CurrencyIndicator</PropertyName>
</FieldValidator>
</ValidatorList>
Not a million miles away from what you wanted. There is the need to output certain things in a certain way (eg xsi:type tells the serializer how to deserialize back to the object map). I hope this gives you a good start.
Here is a live, working example: http://rextester.com/OXPOB95358
Deserialization can be done by calling the Deserialize method on the XmlSerializer.
For example, if your xml is in a string:
var ser = new XmlSerializer(typeof(ValidatorList));
var test = "<..../>" // Your Xml
var xmlReader = XmlReader.Create(new StringReader(test));
var validatorList = (ValidatorList)ser.Deserialize(xmlReader);
There are many overrides of Deserialize which take differing inputs depending if the data is in a Stream an existing reader, or saved to a file.
You have to decorate the class that contains the _validators field with the KonwnType attribute
[Serializable]
[KwownType(typeof(RequiredFieldValidator)]
[KwownType(typeof(AsciValidator)]
public class MySerialisableClass
I have several SO answers that detail how to serialize objects using XML. I'll provide links below.
However, since you're looking for a rather simple serialization of your object, you may want to read up on the DataContractSerializer. It's much less complicated than the old .NET 1.x XML Serialization.
Here's the list of SO answers:
Omitting all xsi and xsd namespaces when serializing an object in .NET?
XmlSerializer: remove unnecessary xsi and xsd namespaces
Suppress xsi:nil but still show Empty Element when Serializing in .Net
Using XmlAttributeOverrides on Nested Properties
Even though many of these have to do with XML serialization and namespaces, they contain complete examples of serializing an object to XML using .NET 1.x XML Serialization.

Categories