Deserialize XML element from InnerText and Value attribute - c#

I have XML Serializable class with property Name
[Serializable]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
and I want it to be able to deserialize XML file that I have in two ways:
<item>
<name>Name</name>
</item>
and
<item>
<name value="Name" />
</item>
The first works fine but what should I do to be able to deserialize the second also with the same class?

XML Serialization attributes work both with serialization and deserialization. If we'll assume that it might be possible to use attributes for deserializing instance of Item from two different xml structures, then how serialization should work - should it serialize instance name to element value, or to attribute? Or to both? That's why you cannot deserialize two different xml structures into single class. Use two different classes or deserialize it manually without usage of XML Serialization attributes.

I found another way to solve my problem using only one class maybe someone will find this useful
[Serializable]
public class Item
{
[XmlElement("name")]
public NameElement NameElement { get; set; }
}
public class NameElement
{
[XmlAttribute("value")]
public string Value { get; set; }
[XmlText]
public string Text { get; set; }
[XmlIgnore]
public string Name
{
get { return String.IsNullOrEmpty(this.Value) ? this.Text : this.Value; }
set { this.Value = value; }
}
}
Maybe it's not super elegant but it works in both cases and uses the same class.

Since you have mentioned that XML data is coming from external sources, so obviously you don't have control over that.
Therefore you can follow any of the option as below:
Create separate class per XML data structure, because as far I know there is no way to control XML Deserialization when using XmlSerializer
You can use XDocument to read the XML by self, to overcome this limitation.
If going by second idea, I have created small Console Application to demonstrate that.
Main piece of code is as below:
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlData));
XDocument doc = XDocument.Load(xmlStream);
var records = from record in doc.Descendants("item").Descendants()
select new Item(!record.IsEmpty ? record.Value : record.Attribute("value").Value);
Here I'm reading the element using LinqToXml and checking if the element is not empty, i.e. Value is not blank, then use Value otherwise read the value from element's Attribute.
Console application (Complete code):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace Console.TestApp
{
class Program
{
static string xmltypeFirst = #"<item>
<name>John</name>
</item>";
static string xmltypeSecond = #"<item>
<name value='Smith' />
</item>";
static void Main(string[] args)
{
var data = xmltypeFirst;
var result = Deserialize(data).ToList();
Console.WriteLine("Name: " + result[0].Name);
data = xmltypeSecond;
result = Deserialize(data).ToList();
Console.WriteLine("Name: " + result[0].Name);
Console.WriteLine("Press any to key to exit..");
Console.ReadLine();
}
private static IEnumerable<Item> Deserialize(string xmlData)
{
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlData));
XDocument doc = XDocument.Load(xmlStream);
var records = from record in doc.Descendants("item").Descendants()
select new Item(!record.IsEmpty ? record.Value : record.Attribute("value").Value);
return records;
}
}
[Serializable]
public class Item
{
public Item(string name)
{
this.Name = name;
}
[XmlElement("name")]
public string Name { get; set; }
}
}
Note: To run this you will need to add reference to System.Xml.Linq.dll in your project.
Reference: here

Related

Cannot load internal nodes from XML file

I have a xml schema in the form of
<?xml version="1.0" encoding="UTF-8"?>
<project ref="edward" name="Edward(A)">
<desc/>
<Zones>
<Zone ref="1" name="Zone1"/>
<Zone ref="2" name="Zone2"/>
<Zone ref="3" name="Zone3"/>
<Zone ref="4" name="Zone4"/>
</Zones>
</project>
I am trying to extract all the Zones value using Xml to Linq (not an expert)
I tried
string xmlString = System.IO.File.ReadAllText("..\\..\\..\\v1.xml");
XDocument xdoc = new XDocument();
xdoc=XDocument.Parse(xmlString);
var ele = xdoc.Descendants("Zones")
.Select(x => (string)x.Element("name"))
.FirstOrDefault();
// this is null
var result = xdoc.Element("project").Descendants("Zones").Descendants("Zone");
foreach (var item in result)
{
Console.WriteLine(item.name); //what should be here
}
You could add the items to a list or using a Dictionary<int, string> may look something like...
Dictionary<int, string> Zones = new Dictionary<int, string>();
foreach (var item in result) {
int.TryParse(item.Attribute("ref").Value, out int value);
Zones.Add(value, item.Attribute("name").Value);
Console.WriteLine(item.Attribute("name").Value);
Console.WriteLine(item.Attribute("ref").Value);
}
Another approach using two simple Classes…
Looking closer, I am betting using a couple of classes and the System.Xml.Serialization; library may make this easier. Given the XML file. One Class appears obvious… called a Zone object with the int and string properties. Then another Class we could call ZoneProject.
The ZoneProject Class would have three (3) properties. In this case two string properties called Ref and Name, and a List<Zone> of Zone objects called Zones. The bare minimum of these two classes may look something like…
The Zone Class
public class Zone {
[XmlAttribute("ref")]
public int Ref { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
}
The ZoneProject Class
[XmlRoot("project")]
public class ZoneProject {
[XmlAttribute("ref")]
public string Ref { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
[XmlArray("Zones")]
public List<Zone> Zones { get; set; }
}
To help “deserialize” the XML we added additional qualifiers to help mate the XML element/attributes to the particular property of each class. The XmlRoot is the base class ZoneProject with the XmlAttributes and the collection of Zone objects using the XmlArray qualifier. This property will be a List<Zone> collection. The same follows for the Zone Class.
With this set up, we can use the System.Xml.Serialization; library and deserialize the XML file into a single ZoneProject object. Something like…
string filePath = #"PathToYourXML_File\file.xml";
XmlSerializer serializer = new XmlSerializer(typeof(ZoneProject));
ZoneProject TheZoneProject;
using (FileStream fs = File.OpenRead(filePath)) {
TheZoneProject = (ZoneProject)serializer.Deserialize(fs);
}
It should be noted, that the answer from #Enyra on the SO question How to read XML file into List<>? …was helpful in the answer above.
I hope this makes sense.
Please check your xml file path. I think that , problem here.
string xmlString = System.IO.File.ReadAllText(dicorrectPath);
You can collect you zones attributes with Linq:
var result = xdoc.Element("project").Descendants("Zones").Descendants("Zone");
var zonesAtributes = result.Select(x =>
new
{
name = x.Attribute("name").Value,
#ref = x.Attribute("ref").Value
}).ToArray();

XML serialisation for class properties with additional meta data

I have an entity as below
public class Vehicle{
public int VehicleId {get;set;};
public string Make {get;set;};
public string Model{get;set;}
}
I wanted to serialize as below
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common" >HXV</Make>
<Model AppliesTo="C2">34-34</Model>
</Vehicle>
I have around 100 properties like this in Vehicle class, for each vehicle property I wanted to attach a metadata ApplieTo which will be helpful to downstream systems. AppliesTo attribute is static and its value is defined at the design time. Now How can I attach AppliesTo metadata to each property and inturn get serialized to XML?
You can use XElement from System.Xml.Linq to achieve this. As your data is static you can assign them easily. Sample code below -
XElement data= new XElement("Vehicle",
new XElement("VehicleId", new XAttribute("AppliesTo", "C1"),"1244"),
new XElement("Make", new XAttribute("AppliesTo", "Common"), "HXV"),
new XElement("Model", new XAttribute("AppliesTo", "C2"), "34 - 34")
);
//OUTPUT
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common">HXV</Make>
<Model AppliesTo="C2">34 - 34</Model>
</Vehicle>
If you are not interested in System.Xml.Linq then you have another option of XmlSerializer class. For that you need yo define separate classes for each property of vehicle. Below is the sample code and you can extend the same for Make and Model -
[XmlRoot(ElementName = "VehicleId")]
public class VehicleId
{
[XmlAttribute(AttributeName = "AppliesTo")]
public string AppliesTo { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "Vehicle")]
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")]
public VehicleId VehicleId { get; set; }
//Add other properties here
}
Then create test data and use XmlSerializer class to construct XML -
Vehicle vehicle = new Vehicle
{
VehicleId = new VehicleId
{
Text = "1244",
AppliesTo = "C1",
}
};
XmlSerializer testData = new XmlSerializer(typeof(Vehicle));
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sww))
{
testData.Serialize(writer, vehicle);
xml = sww.ToString(); // XML
}
}
It is not easy or ideal to use the default .NET XML serializer (System.Xml.Serialization.XmlSerializer) in the way you want, but it's possible. This answer shows how to create a class structure to hold both your main data and the metadata, then use XmlAttributeAttribute to mark a property so it gets serialized as an XML attribute.
Assumptions:
There are a number of unknowns about your intended implementation, such as:
The XML serializer you want to use (default for .NET?)
The mechanism to inject 'AppliesTo' (attribute?)
Do you care about deserialization?
This answer assumes the default .NET serializer, that deserialization matters, and that you don't care about the exact method of injecting your metadata.
Key concepts:
A generic class to hold both our main property value and the metadata (see PropertyWithAppliesTo<T>)
Using XmlAttributeAttribute on the generic class' metadata, so it is written as an XML attribute on the parent property
Using XmlTextAttribute on the generic class' main data, so it is written as the Xml text of the parent property (and not as a sub-property)
Including two properties on the main type being serialized (in this case Vehicle) for every value you want serialized: one of the new generic type that gets serialized with metadata, and one of the original type marked with XmlIgnoreAttribute that provides 'expected' access to the property's value
Using the XmlElementAttribute to change the name of the serialized property (so it matches the expected name)
Code:
using System;
using System.IO;
using System.Xml.Serialization;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Vehicle));
string s;
var vehicle = new Vehicle { VehicleId = 1244 };
//serialize
using (var writer = new StringWriter())
{
serializer.Serialize(writer, vehicle);
s = writer.ToString();
Console.WriteLine(s);
}
// edit the serialized string to test deserialization
s = s.Replace("Common", "C1");
//deserialize
using (var reader = new StringReader(s))
{
vehicle = (Vehicle)serializer.Deserialize(reader);
Console.WriteLine($"AppliesTo attribute for VehicleId: {vehicle.VehicleIdMeta.AppliesTo}");
}
}
}
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")] // renames to remove the 'Meta' string
public PropertyWithAppliesTo<int> VehicleIdMeta { get; set; } = new PropertyWithAppliesTo<int>("Common");
[XmlIgnore] // this value isn't serialized, but the property here for easy syntax
public int VehicleId
{
get { return VehicleIdMeta.Value; }
set { VehicleIdMeta.Value = value; }
}
}
public class PropertyWithAppliesTo<T>
{
[XmlAttribute] // tells serializer this should be an attribute on this element, not a property
public string AppliesTo { get; set; } = string.Empty;
[XmlText] // tells serializer to not write this as a property, but as the main XML text
public T Value { get; set; } = default;
public PropertyWithAppliesTo() : this(string.Empty) { }
public PropertyWithAppliesTo(string appliesTo) : this(appliesTo, default) { }
public PropertyWithAppliesTo(string appliesTo, T initialValue)
{
AppliesTo = appliesTo;
Value = initialValue;
}
}
}
When run, the string s will look like:
<?xml version=\"1.0\" encoding=\"utf-16\"?>
<Vehicle xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
<VehicleId AppliesTo="Common">1244</VehicleId>
</Vehicle>
Other Notes:
You can see how to add more properties to Vehicle: add a property of type PropertyWithAppliesTo<T> marked with XmlElement to give it the name you want, and then a property of type T marked with XmlIgnore that wraps around the Value you want.
You can control the value of AppliesTo by changing the input to the constructor of PropertyWithAppliesTo<T> and giving it a different metadata string.
If you don't want consumers of your library to see the 'meta' properties in IntelliSense, you can use the EditorBrowsableAttribute. It won't hide things from you when using the source and a project reference; it's only hidden when referencing the compiled dll.
This is admittedly an annoying way to add properties to a class. But if you want to use the default .NET XML serializer, this is a way to achieve the XML you want.

Can I avoid cluttering my class with repetitive XmlElement attributes even though the root node is in a different namespace than its children?

I am retrieving XML documents from a web service I have no control over. The XML is formatted similarly to the following:
<?xml version="1.0"?>
<ns:obj xmlns:ns="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</ns:obj>
where the root node is in the "ns" namespace, but none of its child elements are.
After some trial and error, I found that I could deserialize the document to a C# object by doing the following:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
public class xmlObject
{
[XmlElement(Namespace = "")]
public string address { get; set; }
[XmlElement(Namespace = "")]
public int residents { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\"?>" +
"<ns:obj xmlns:ns=\"somenamespace\">" +
" <address>1313 Mockingbird Lane</address>" +
" <residents>5</residents>" +
"</ns:obj>";
var serializer = new XmlSerializer(typeof(xmlObject));
using (var reader = new StringReader(xml))
{
var result = serializer.Deserialize(reader) as xmlObject;
Console.WriteLine("{0} people live at {1}", result.residents, result.address);
// Output: "5 people live at 1313 Mockingbird lane"
}
}
}
If I omit the XmlElementAttribute on the individual members, I instead get an empty object. I.e. The output reads
0 people live at
(result.address is equal to null.)
I understand the rationale behind why the deserialization process works like this, but I'm wondering if there is a less verbose way to tell XmlSerializer that the child elements of the object are not in the same namespace as the root node.
The objects I'm working with in production have dozens of members and, for cleanliness sake, I'd like to avoid tagging all of them with [XmlElement(Namespace = "")] if it's easily avoidable.
You can combine XmlRootAttribute with XmlTypeAttribute to make it so the root element, and the root element's elements, have different namespaces:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
[XmlType(Namespace="")]
public class xmlObject
{
public string address { get; set; }
public int residents { get; set; }
}
Using the type above, if I deserialize and re-serialize your XML I get:
<q1:obj xmlns:q1="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</q1:obj>
Sample fiddle.
If you know the contract with the web service, why not use a DataContractSerializer to deserialize the XML into the objects?

Deserialization XML to object with list in c#

I want to deserialize XML to object in C#, object has one string property and list of other objects.
There are classes which describe XML object, my code doesn't work (it is below, XML is at end of my post). My Deserialize code doesn't return any object.
I think I do something wrong with attributes, could you check it and give me some advice to fix it.
Thanks for your help.
[XmlRoot("shepherd")]
public class Shepherd
{
[XmlElement("name")]
public string Name { get; set; }
[XmlArray(ElementName = "sheeps", IsNullable = true)]
[XmlArrayItem(ElementName = "sheep")]
public List<Sheep> Sheeps { get; set; }
}
public class Sheep
{
[XmlElement("colour")]
public string colour { get; set; }
}
There is C# code to deserialize XML to objects
var rootNode = new XmlRootAttribute();
rootNode.ElementName = "createShepherdRequest";
rootNode.Namespace = "http://www.sheeps.pl/webapi/1_0";
rootNode.IsNullable = true;
Type deserializeType = typeof(Shepherd[]);
var serializer = new XmlSerializer(deserializeType, rootNode);
using (Stream xmlStream = new MemoryStream())
{
doc.Save(xmlStream);
var result = serializer.Deserialize(xmlStream);
return result as Shepherd[];
}
There is XML example which I want to deserialize
<?xml version="1.0" encoding="utf-8"?>
<createShepherdRequest xmlns="http://www.sheeps.pl/webapi/1_0">
<shepherd>
<name>name1</name>
<sheeps>
<sheep>
<colour>colour1</colour>
</sheep>
<sheep>
<colour>colour2</colour>
</sheep>
<sheep>
<colour>colour3</colour>
</sheep>
</sheeps>
</shepherd>
</createShepherdRequest>
XmlRootAttribute does not change the name of the tag when used as an item. The serializer expects <Shepherd>, but finds <shepherd> instead. (XmlAttributeOverrides does not seem to work on arrays either.) One way to to fix it, is by changing the case of the class-name itself:
public class shepherd
{
// ...
}
An easier alternative to juggling with attributes, is to create a proper wrapper class:
[XmlRoot("createShepherdRequest", Namespace = "http://www.sheeps.pl/webapi/1_0")]
public class CreateShepherdRequest
{
[XmlElement("shepherd")]
public Shepherd Shepherd { get; set; }
}

One field xml cannot be deserialized using XmlSerializer class

I have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<connection_state>conn_state</connection_state>
Following the msdn, I must describe it as a type for correct deserialization using XmlSerializer. So the class name points the first tag, and its fields subtags.
For example:
public class connection_state
{
public string state;
}
Will be transformed into the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<connection_state>
<state>conn_state</state>
</connection_state>
But the xml I receive has only one tag. And we cannot create a field with the name of its class like:
public class connection_state
{
public string connection_state;
}
Or can?
Is there any solution for this issue?
Proper Xml has a root element with no content except other elements. If you are stuck with that tiny one-tag psuedo-XML, is there a reason you need to use XmlSerializer? Why not just create a class with a constructor that takes the literal "Xml" string:
using System.Xml.Linq;
public class connection_state {
public string state { get; set; }
public connection_state(string xml) {
this.state = XDocument.Parse(xml).Element("connection_state").Value;
}
}
Edit:
In response to OP's comment: You don't have to us an XmlSerializer; you can just read the ResponseStream directly and pass that to your connection_state constructor:
String xmlString = (new StreamReader(webResponse.GetResponseStream())).ReadToEnd();
connection_state c= new connection_state(xmlString);
Replace
public class connection_state
{
public string state;
}
to
public class connection_state
{
public string state {set; get;}
}

Categories

Resources