Deserialize a portion of xml into classes - c#

So if I have 3 different files that contain extremely long and complex xml (which i've abbreviated here) but just the root element name is different (not really random but I'd rather not duplicate classes for the same xml):
File 1
<?xml version="1.0"?>
<somerootname1>
<car>
<make>honda</make>
<model>civic</model>
</car>
</somerootname1>
File 2
<?xml version="1.0"?>
<somerootname2>
<car>
<make>honda</make>
<model>civic</model>
</car>
</somerootname2>
File 3
<?xml version="1.0"?>
<somerootname3>
<car>
<make>honda</make>
<model>civic</model>
</car>
</somerootname3>
Because XmlRootAttribute has AllowMultiple=false I can't create a single class to represent the previous xml.
Duplicate 'XmlRootAttribute' attribute
[XmlRoot(ElementName="somerootname1")]
[XmlRoot(ElementName="somerootname2")]
public class Root
{
public Automobile Car { get; set; }
}
public class Automobile
{
public string Make { get; set; }
public string Model { get; set; }
}
I thought I might be able to be sneaky and grab just the element I need and deserialize it:
var xDoc = XDocument.Parse("myfile.xml");
var xCar = xDoc.Root.Descendants()
.FirstOrDefault(d => d.Name.LocalName.Equals("car"))
var serializer = new XmlSerializer(typeof(Automobile))
using (var reader = xml.CreateReader())
{
result = (Automobile)serializer.Deserialize(reader);
}
However that results in an InvalidOperationException:
There is an error in XML document (0, 0).
With an InnerException:
<car> was not expected.
It seems really hacky to either; rename the root element and then deserialize OR take the element and prefix the <?xml> tag. Any other suggestions?

You have extracted the <car> node, but you're still deserializing from the document's root node. That's why the exception suggests that it's expecting the <car> node. The following works for me:
[XmlRoot("car")]
public class Automobile
{
[XmlElement("make")]
public string Make { get; set; }
[XmlElement("model")]
public string Model { get; set; }
}
Deserialize from <car>:
var xCar = xDoc.Root.Descendants()
.FirstOrDefault(d => d.Name.LocalName.Equals("car"));
var serializer = new XmlSerializer(typeof(Automobile));
using (var reader = xCar.CreateReader())
{
var result = (Automobile)serializer.Deserialize(reader);
Console.WriteLine(result.Make);
Console.WriteLine(result.Model);
}

I did not come up with this but just tweaked it some, the original is from: Deserialize XML To Object using Dynamic
Not sure if it is exactly what you need, but it would allow you to grab the values from the xml and put them into your class. It can be adjusted for arrays or collections as well, if there were multiple cars in one xml doc.
You will need to add:
using System.Dynamic;
using System.Xml.Linq;
protected Car ParseXml(Car NewCar) {
string xml = "<?xml version=\"1.0\"?><somerootname1> <car> <make>honda</make> <model>civic</model> </car></somerootname1>";
dynamic car = DynamicXml.Parse(xml);
NewCar.Make = car.car.make;
NewCar.Model = car.car.model;
return NewCar;
}
[Serializable]
public partial class Car
{
public Car() { }
public string Make { get; set; }
public string Model { get; set; }
}
public class DynamicXml : DynamicObject
{
XElement _root;
private DynamicXml(XElement root)
{
_root = root;
}
public static DynamicXml Parse(string xmlString)
{
return new DynamicXml(XDocument.Parse(xmlString).Root);
}
public static DynamicXml Load(string filename)
{
return new DynamicXml(XDocument.Load(filename).Root);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
var att = _root.Attribute(binder.Name);
if (att != null)
{
result = att.Value;
return true;
}
var nodes = _root.Elements(binder.Name);
if (nodes.Count() > 1)
{
result = nodes.Select(n => new DynamicXml(n)).ToList();
return true;
}
var node = _root.Element(binder.Name);
if (node != null)
{
if (node.HasElements)
{
result = new DynamicXml(node);
}
else
{
result = node.Value;
}
return true;
}
return true;
}
}

Related

XmlSerializer access child logical elements in parent element

I am using the standard .NET XmlSerializer to deserialize the following xml:
<root>
<Element>
<Grouping1>
<Item1>First</Item1>
<Item2>Second</Item2>
</Grouping1>
<Grouping2>
<Item3>Third</Item3>
</Grouping2>
</Element>
</root>
I would like to serialize it into the following class:
class Element
{
[XmlElement("Item1")]
public string Item1 { get; set; }
[XmlElement("Item2")]
public string Item2 { get; set; }
[XmlElement("Item3")]
public string Item3 { get; set; }
}
Which of course doesn't work, because - for instance - <Item1> isn't located in <Element> but in the logical container <Grouping1>.
The question:
Is there a way of telling the XmlSerializer to look for the Item1 in the <Grouping1> element?
Something downs the lines of [XmlElement("Grouping1.Item1")]
PS: I don't want to create a Grouping1 class (as suggested here) because the groupings are only logical containers and shouldn't have their own class.
Using Xml Linq. A custom serializer would be much more complicated
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication4
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<Element> elements = doc.Descendants("Element").Select(x => new Element()
{
Item1 = (string)x.Descendants("Item1").FirstOrDefault(),
Item2 = (string)x.Descendants("Item2").FirstOrDefault(),
Item3 = (string)x.Descendants("Item3").FirstOrDefault()
}).ToList();
}
}
class Element
{
public string Item1 { get; set; }
public string Item2 { get; set; }
public string Item3 { get; set; }
}
}
Here is what serializer would look like
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml.Schema;
namespace ConsoleApplication4
{
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
{
[XmlElement("Element")]
public List<Element> Element { get; set; }
}
public class Element : IXmlSerializable
{
private string Item1 { get; set; }
private string Item2 { get; set; }
private string Item3 { get; set; }
public void WriteXml(XmlWriter writer)
{
XElement element = new XElement("Element", new object[] {
new XElement("Grouping1", new object[] {
new XElement("Item1", Item1),
new XElement("Item2", Item2)
}),
new XElement("Grouping2", new XElement("Item3", Item3))
});
element.WriteTo(writer);
}
public void ReadXml(XmlReader reader)
{
XElement element = (XElement)XElement.ReadFrom(reader);
Item1 = (string)element.Descendants("Item1").FirstOrDefault();
Item2 = (string)element.Descendants("Item2").FirstOrDefault();
Item3 = (string)element.Descendants("Item3").FirstOrDefault();
}
public XmlSchema GetSchema()
{
return (null);
}
}
}
I don't want to create a Grouping1 class...
Not sure whether that's possible using serialization without creating the objects that output this XML.
An alternative to serialization is using the XmlReader to extract the properties in question (Item1, Item2, and Item3) and create a list of Element type, and XmlWriter to generate the whole XML file. Both classes provide fast, non-cached, and forward-only way to read and write XML files.
Assuming your XML file has multiple Element entries like:
<root>
<Element>
<Grouping1>
<Item1>First1</Item1>
<Item2>Second1</Item2>
</Grouping1>
<Grouping2>
<Item3>Third1</Item3>
</Grouping2>
</Element>
<Element>
<Grouping1>
<Item1>First2</Item1>
<Item2>Second2</Item2>
</Grouping1>
<Grouping2>
<Item3>Third2</Item3>
</Grouping2>
</Element>
</root>
... and a class named Element:
//The serializable attribute is not required here...
public class Element
{
public Element() { }
public string Item1 { get; set; }
public string Item2 { get; set; }
public string Item3 { get; set; }
public override string ToString() => $"{Item1}, {Item2}, {Item3}";
}
Create a function to read the file, create and return a list of Element items:
public List<Element> ReadElements(string xmlFile)
{
var elements = new List<Element>();
Element ele = null;
using (var xr = XmlReader.Create(xmlFile))
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element)
{
if (xr.Name == "Element")
ele = new Element();
else if (xr.Name == "Item1")
{
xr.Read();
ele.Item1 = xr.Value;
}
else if (xr.Name == "Item2")
{
xr.Read();
ele.Item2 = xr.Value;
}
else if (xr.Name == "Item3")
{
xr.Read();
ele.Item3 = xr.Value;
}
}
else if (xr.NodeType == XmlNodeType.EndElement)
if (xr.Name == "Element")
elements.Add(ele);
}
return elements;
}
... and a method to write:
public void WriteElements(string xmlFile, IEnumerable<Element> elements)
{
var xmlSet = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true,
WriteEndDocumentOnClose = true,
};
using (var xr = XmlWriter.Create(xmlFile, xmlSet))
{
xr.WriteStartElement("root");
foreach(var ele in elements)
{
xr.WriteStartElement("Element");
xr.WriteStartElement("Grouping1");
xr.WriteStartElement("Item1");
xr.WriteString(ele.Item1);
xr.WriteEndElement();
xr.WriteStartElement("Item2");
xr.WriteString(ele.Item2);
xr.WriteEndElement();
xr.WriteEndElement();
xr.WriteStartElement("Grouping2");
xr.WriteStartElement("Item3");
xr.WriteString(ele.Item3);
xr.WriteEndElement();
xr.WriteEndElement();
xr.WriteEndElement();
}
}
}
A test to read and write the file like:
private void TheCaller()
{
var xmlFile = "XmlFile.xml";
var elements = ReadElements(xmlFile);
elements.ForEach(x => Console.WriteLine(x));
//...
WriteElements(xmlFile, elements);
}
Prints in my output window:
First1, Second1, Third1
First2, Second2, Third2

C# Parse XML to Object and store into Database

I'm currently working on an ASP.NET MVC 4.6 application using SQL Server 2014 as a data storage.
I need to parse a XML document from an URL, transform the XML into an object and store it into the database using Entity Framework 6.
I need to parse the XML from an URL like this:
http: //api.myserver.com/notes.xml
My XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<notes>
<note>
<to>Tove</to>
<from>Jane</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
<note>
<to>Doe</to>
<from>John</from>
<heading>Meeting</heading>
<body>Hello Monday!</body>
</note>
<notes>
My model class note looks like this:
public class Note
{
public string To { get; set; }
public string From { get; set; }
public string Heading { get; set; }
public string Body { get; set; }
}
My current test implementation looks like this, BUT I'm not so happy about the parsing of the XML:
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
String url = "https://www.w3schools.com/xml/note.xml";
XmlDocument doc = new XmlDocument();
doc.Load(url);
XmlElement root = doc.DocumentElement;
StringBuilder sb = new StringBuilder();
sb.Append("<note>");
foreach (XmlNode item in root)
{
sb.Append(item.OuterXml);
}
sb.Append("</note>");
var result = Deserialize<Note>(sb.ToString());
// further processing of result using EF 6
Console.ReadKey();
}
public static T Deserialize<T>(string xmlText)
{
try
{
var stringReader = new StringReader(xmlText);
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
catch (Exception ex)
{
throw;
}
}
}
[XmlRoot(ElementName = "note", Namespace = "")]
public class Note
{
[XmlElement("to")]
public string To { get; set; }
[XmlElement("from")]
public string From { get; set; }
[XmlElement("heading")]
public string Heading { get; set; }
[XmlElement("body")]
public string Body { get; set; }
}
}
As far as I noticed, .NET provides several approaches on how to parse XML:
XmlReader Class
XPath
XDocument
DataSet
LINQ
I was wondering, what would you use and how would u solve the parsing of the XML document from an URL using .NET and converting into an object to store it in a SQL Server DB?
Thanks for your help and consideration!!
This might help you, a serialize method and a Deserialize method.
public static string Serialize<T>(T dataToSerialize)
{
try
{
var stringwriter = new ISOEncodingStringWriter();
var serializer = new XmlSerializer(typeof(T));
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
serializer.Serialize(stringwriter, dataToSerialize, xns);
return stringwriter.ToString();
}
catch
{
throw;
}
}
public static T Deserialize<T>(string xmlText)
{
try
{
var stringReader = new System.IO.StringReader(xmlText);
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
catch
{
throw;
}
}
You use the method like this:
Note result = Deserialize<Note>(xmlstring);
Note note = new Note(){...};
string convertedToXML = Serialize<Note>(note);
Just remember that you need to add some data to your note class so it can actually serialize the data:
[XmlRoot(ElementName = "root", Namespace = "")]
public class Note
{
[XmlElementAttribute("To")]
public string To { get; set; }
[XmlElementAttribute("From")]
public string From { get; set; }
[XmlElementAttribute("Heading")]
public string Heading { get; set; }
[XmlElementAttribute("Body")]
public string Body { get; set; }
}
I hope it helps :)
You can use LINQ to XML to traverse the nodes of your documents and either EF or ADO.NET to store them in the DB.
You can start with this helpful tutorial: http://www.c-sharpcorner.com/UploadFile/de41d6/learning-linq-made-easy-tutorial-1/

Deserializing XML Into class with different members C#

I am facing a problem in deserializing xml into a class object. The class has a slightly different structure than the xml, so am unable to deserialize it. Following is the main code
public class Customer
{
[XmlElement(ElementName = "CustomerName")]
public string Name { get; set; }
}
public class XmlCheck
{
[XmlElement(ElementName = "Customer")]
public Customer Customer { get; set; }
public List<Customer> CustomersList { get; set; }
}
class Program
{
static string xml = #"<?xml version=""1.0"" ?>
<XmlCheck>
<Customer>
<CustomerName>Omer</CustomerName>
</Customer>
<Customer>
<CustomerName>Ali</CustomerName>
</Customer>
</XmlCheck>";
static void Main(string[] args)
{
var serializer = new XmlSerializer(typeof(XmlCheck), new XmlRootAttribute("XmlCheck"));
using (var stringReader = new StringReader(xml))
using (var reader = XmlReader.Create(stringReader))
{
var xmlResult = (XmlCheck)serializer.Deserialize(reader);
xmlResult.CustomersList.Add(xmlResult.Customer);
Console.WriteLine(xmlResult.Customer.Name);
}
}
Is there any way, to deserialize the xml into the customers list without having to insert that node inside the xml? Currently this only deserializes the first customer node that has name as 'Omer' and it adds that to the list.
I know how to accomplish the above by writing a custom xml reader, but need to use xml deserialization for this. However, if this isn't possible using xml deserialization, any way to achieve this using any customer (s/de)erializer?
Please try this:
public class Customer
{
[XmlElement(ElementName = "CustomerName")]
public string Name { get; set; }
}
[XmlRoot("XmlCheck")]
public class XmlCheck
{
[XmlElement(ElementName = "Customer")]
public List<Customer> CustomersList { get; set; }
}
class Program
{
static string xml = #"<?xml version=""1.0"" ?>
<XmlCheck>
<Customer>
<CustomerName>Omer</CustomerName>
</Customer>
<Customer>
<CustomerName>Ali</CustomerName>
</Customer>
</XmlCheck>";
static void Main(string[] args)
{
var serializer = new XmlSerializer(typeof(XmlCheck), new XmlRootAttribute("XmlCheck"));
using (var stringReader = new StringReader(xml))
using (var reader = XmlReader.Create(stringReader))
{
var xmlResult = (XmlCheck)serializer.Deserialize(reader);
//xmlResult.CustomersList.Add(xmlResult.Customer);
foreach(var c in xmlResult.CustomersList)
{
Console.WriteLine(c.Name);
}
}
}
}
I got it from: Is it possible to deserialize XML into List<T>?

How to serialize attribute on a property or skip a class hierarchy

I have a class that I am serializing using XmlSerializer. In addition to other properties, I need to add a chunk of pre-built xml to the object. I already asked how to handle that chuck of xml in this post: How to remove empty namespace attribute on manually added xml string when serializing object?
Now I need to add an attribute to the property that contains the xml string. I understand how to add an attribute to a class but not to a property. If I create a new class to hold the attribute, I get an extra hierarchy in my output.
Here is my simplified code:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public XmlElement Extension { get; set; }
public Book()
{
}
public void AddExtension()
{
string xmlString = "<AdditionalInfo xmlns=\"http://www.somenamespace.com\">" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";
this.Extension = GetElement(xmlString);
}
public static XmlElement GetElement(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.DocumentElement;
}
}
static void Main(string[] args)
{
TestSerialization p = new TestSerialization();
Book bookOne = new Book();
bookOne.Title = "How to Fix Code";
bookOne.Author = "Dee Bugger";
bookOne.AddExtension();
System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(Book), "http://www.somenamespace.com");
using (var writer = new StreamWriter("C:\\BookReport.xml"))
using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = true }))
{
serializer.Serialize(xmlWriter, bookOne);
}
}
It generates the following output:
<?xml version="1.0" encoding="utf-8"?>
<Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.somenamespace.com">
<Title>How to Fix Code</Title>
<Author>Dee Bugger</Author>
<Extension>
<AdditionalInfo xmlns="http://www.somenamespace.com">
<SpecialHandling>Some Value</SpecialHandling>
</AdditionalInfo>
</Extension>
</Book>
Now I need to add an attribute to Extension to make the Extension output look like:
...
<Extension Modifier="ABC">
<AdditionalInfo xmlns="http://www.somenamespace.com">
<SpecialHandling>Some Value</SpecialHandling>
</AdditionalInfo>
</Extension>
Is there a way to change the Book class to do this? I tried to create an Extension class to hold the Modifier attribute and the XmlElement of the xml string but that resulted in an extra level:
public class Extension
{
[XmlAttribute]
public string Modifier { get; set; }
[XmlElementAttribute("Extension")]
public XmlElement ExtensionAsElement { get; set; }
public Extension()
{
}
public Extension(XmlElement extensionAsElement)
{
this.Modifier = "ABC";
this.ExtensionAsElement = extensionAsElement;
}
}
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
[XmlElementAttribute("Extension")]
public Extension ExtensionObj { get; set; }
public Book()
{
}
public void AddExtension()
{
string xmlString = "<AdditionalInfo xmlns=\"http://www.somenamespace.com\">" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";
this.ExtensionObj = new Extension(GetElement(xmlString));
}
public static XmlElement GetElement(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.DocumentElement;
}
}
<?xml version="1.0" encoding="utf-8"?>
<Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.somenamespace.com">
<Title>How to Fix Code</Title>
<Author>Dee Bugger</Author>
<Extension Modifier="ABC">
<Extension>
<AdditionalInfo xmlns="http://www.somenamespace.com">
<SpecialHandling>Some Value</SpecialHandling>
</AdditionalInfo>
</Extension>
</Extension>
</Book>
Note: this is an overly simplified example of my code, the Book class is not my root. I only need to serialize, not deserialize.
You can use [XmlAnyElement("Extension")] to specify that your Extension property should be inserted as-is into the XML stream as the element <Extension> itself, rather than as a child of an element of that name. Having done so, you will be able to set attributes on the element itself using SetAttribute() and GetAttribute().
Thus your Book class becomes something like:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
[XmlAnyElement("Extension")]
public XmlElement Extension { get; set; }
public Book()
{
this.Extension = new XmlDocument().CreateElement("Extension");
}
public Book AddExtension()
{
string innerXmlString = "<AdditionalInfo xmlns=\"http://www.somenamespace.com\">" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";
if (Extension == null)
// Since Extension is marked with [XmlAnyElement("Extension")], its value must
// be an XmlElement named "Extension". Its InnerXml can be anything.
Extension = new XmlDocument().CreateElement("Extension");
Extension.InnerXml = innerXmlString;
return this;
}
const string ModifierName = "Modifier";
[XmlIgnore]
public string Modifier
{
get
{
if (Extension == null)
return null;
return Extension.GetAttribute(ModifierName);
}
set
{
if (Extension == null)
AddExtension();
if (value == null)
Extension.RemoveAttribute(ModifierName);
else
Extension.SetAttribute(ModifierName, value);
}
}
}
And creating XML from the following:
var bookOne = new Book { Title = "How to Fix Code", Author = "Dee Bugger", Modifier = "AAA" }
.AddExtension();
Produces the result:
<Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>How to Fix Code</Title>
<Author>Dee Bugger</Author>
<Extension Modifier="AAA">
<AdditionalInfo xmlns="http://www.somenamespace.com">
<SpecialHandling>Some Value</SpecialHandling>
</AdditionalInfo>
</Extension>
</Book>

Can I specify the element name of the items when I inherit from List<T>

I am trying to figure out a way to specify the element name for serialization when I inherit from List
class
{
[XmlRoot(ElementName = "Foo")]
public class Widget
{
public int ID { get; set; }
}
[XmlRoot(ElementName = "Foos")]
public class WidgetList : List<Widget>
{
}
}
public static XElement GetXElement(object obj)
{
using (var memoryStream = new MemoryStream())
{
using (TextWriter streamWriter = new StreamWriter(memoryStream))
{
var xmlSerializer = new XmlSerializer(obj.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("dn", "http://defaultnamespace");
xmlSerializer.Serialize(streamWriter, obj, ns);
return XElement.Parse(Encoding.ASCII.GetString(memoryStream.ToArray()));
}
}
}
static void Main(string[] args)
{
WidgetList list = new WidgetList();
list.Add(new Widget { ID = 0 });
list.Add(new Widget { ID = 1 });
XElement listElement = GetXElement(list);
Console.WriteLine(listElement.ToString());
Console.ReadKey();
}
Result:
<Foos xmlns:dn="http://defaultnamespace">
<Widget>
<ID>0</ID>
</Widget>
<Widget>
<ID>1</ID>
</Widget>
</Foos>
Desired Result:
<Foos xmlns:dn="http://defaultnamespace">
<Foo>
<ID>0</ID>
</Foo>
<Foo>
<ID>1</ID>
</Foo>
</Foos>
I was mostly wondering if I could modify "GetXElement" to respect the XmlRoot attribute of "Widget", but I am open to other ideas as long as I can still inherit from list. I do not like the solution given here: Serialize a generic collection specifying element names for items in the collection
[XmlType(TypeName = "Foo")]
[Serializable]
public class Widget
{
public int ID { get; set; }
}

Categories

Resources