How to deserialize a given xml document [duplicate] - c#

How do I Deserialize this XML document:
<?xml version="1.0" encoding="utf-8"?>
<Cars>
<Car>
<StockNumber>1020</StockNumber>
<Make>Nissan</Make>
<Model>Sentra</Model>
</Car>
<Car>
<StockNumber>1010</StockNumber>
<Make>Toyota</Make>
<Model>Corolla</Model>
</Car>
<Car>
<StockNumber>1111</StockNumber>
<Make>Honda</Make>
<Model>Accord</Model>
</Car>
</Cars>
I have this:
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElementAttribute("StockNumber")]
public string StockNumber{ get; set; }
[System.Xml.Serialization.XmlElementAttribute("Make")]
public string Make{ get; set; }
[System.Xml.Serialization.XmlElementAttribute("Model")]
public string Model{ get; set; }
}
.
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlArrayItem(typeof(Car))]
public Car[] Car { get; set; }
}
.
public class CarSerializer
{
public Cars Deserialize()
{
Cars[] cars = null;
string path = HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data/") + "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Cars[]));
StreamReader reader = new StreamReader(path);
reader.ReadToEnd();
cars = (Cars[])serializer.Deserialize(reader);
reader.Close();
return cars;
}
}
that don't seem to work :-(

How about you just save the xml to a file, and use xsd to generate C# classes?
Write the file to disk (I named it foo.xml)
Generate the xsd: xsd foo.xml
Generate the C#: xsd foo.xsd /classes
Et voila - and C# code file that should be able to read the data via XmlSerializer:
XmlSerializer ser = new XmlSerializer(typeof(Cars));
Cars cars;
using (XmlReader reader = XmlReader.Create(path))
{
cars = (Cars) ser.Deserialize(reader);
}
(include the generated foo.cs in the project)

Here's a working version. I changed the XmlElementAttribute labels to XmlElement because in the xml the StockNumber, Make and Model values are elements, not attributes. Also I removed the reader.ReadToEnd(); (that function reads the whole stream and returns a string, so the Deserialize() function couldn't use the reader anymore...the position was at the end of the stream). I also took a few liberties with the naming :).
Here are the classes:
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement("StockNumber")]
public string StockNumber { get; set; }
[System.Xml.Serialization.XmlElement("Make")]
public string Make { get; set; }
[System.Xml.Serialization.XmlElement("Model")]
public string Model { get; set; }
}
[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
[XmlArray("Cars")]
[XmlArrayItem("Car", typeof(Car))]
public Car[] Car { get; set; }
}
The Deserialize function:
CarCollection cars = null;
string path = "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();
And the slightly tweaked xml (I needed to add a new element to wrap <Cars>...Net is picky about deserializing arrays):
<?xml version="1.0" encoding="utf-8"?>
<CarCollection>
<Cars>
<Car>
<StockNumber>1020</StockNumber>
<Make>Nissan</Make>
<Model>Sentra</Model>
</Car>
<Car>
<StockNumber>1010</StockNumber>
<Make>Toyota</Make>
<Model>Corolla</Model>
</Car>
<Car>
<StockNumber>1111</StockNumber>
<Make>Honda</Make>
<Model>Accord</Model>
</Car>
</Cars>
</CarCollection>

You have two possibilities.
Method 1. XSD tool
Suppose that you have your XML file in this location C:\path\to\xml\file.xml
Open Developer Command Prompt
You can find it in Start Menu > Programs > Microsoft Visual Studio 2012 > Visual Studio Tools
Or if you have Windows 8 can just start typing Developer Command Prompt in Start screen
Change location to your XML file directory by typing cd /D "C:\path\to\xml"
Create XSD file from your xml file by typing xsd file.xml
Create C# classes by typing xsd /c file.xsd
And that's it! You have generated C# classes from xml file in C:\path\to\xml\file.cs
Method 2 - Paste special
Required Visual Studio 2012+
Copy content of your XML file to clipboard
Add to your solution new, empty class file (Shift+Alt+C)
Open that file and in menu click Edit > Paste special > Paste XML As Classes
And that's it!
Usage
Usage is very simple with this helper class:
using System;
using System.IO;
using System.Web.Script.Serialization; // Add reference: System.Web.Extensions
using System.Xml;
using System.Xml.Serialization;
namespace Helpers
{
internal static class ParseHelpers
{
private static JavaScriptSerializer json;
private static JavaScriptSerializer JSON { get { return json ?? (json = new JavaScriptSerializer()); } }
public static Stream ToStream(this string #this)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(#this);
writer.Flush();
stream.Position = 0;
return stream;
}
public static T ParseXML<T>(this string #this) where T : class
{
var reader = XmlReader.Create(#this.Trim().ToStream(), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Document });
return new XmlSerializer(typeof(T)).Deserialize(reader) as T;
}
public static T ParseJSON<T>(this string #this) where T : class
{
return JSON.Deserialize<T>(#this.Trim());
}
}
}
All you have to do now, is:
public class JSONRoot
{
public catalog catalog { get; set; }
}
// ...
string xml = File.ReadAllText(#"D:\file.xml");
var catalog1 = xml.ParseXML<catalog>();
string json = File.ReadAllText(#"D:\file.json");
var catalog2 = json.ParseJSON<JSONRoot>();

The following snippet should do the trick (and you can ignore most of the serialization attributes):
public class Car
{
public string StockNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
[XmlRootAttribute("Cars")]
public class CarCollection
{
[XmlElement("Car")]
public Car[] Cars { get; set; }
}
...
using (TextReader reader = new StreamReader(path))
{
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
return (CarCollection) serializer.Deserialize(reader);
}

See if this helps:
[Serializable()]
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlArrayItem(typeof(Car))]
public Car[] Car { get; set; }
}
.
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement()]
public string StockNumber{ get; set; }
[System.Xml.Serialization.XmlElement()]
public string Make{ get; set; }
[System.Xml.Serialization.XmlElement()]
public string Model{ get; set; }
}
And failing that use the xsd.exe program that comes with visual studio to create a schema document based on that xml file, and then use it again to create a class based on the schema document.

I don't think .net is 'picky about deserializing arrays'. The first xml document is not well formed.
There is no root element, although it looks like there is. The canonical xml document has a root and at least 1 element (if at all). In your example:
<Root> <-- well, the root
<Cars> <-- an element (not a root), it being an array
<Car> <-- an element, it being an array item
...
</Car>
</Cars>
</Root>

try this block of code if your .xml file has been generated somewhere in disk and if you have used List<T>:
//deserialization
XmlSerializer xmlser = new XmlSerializer(typeof(List<Item>));
StreamReader srdr = new StreamReader(#"C:\serialize.xml");
List<Item> p = (List<Item>)xmlser.Deserialize(srdr);
srdr.Close();`
Note: C:\serialize.xml is my .xml file's path. You can change it for your needs.

For Beginners
I found the answers here to be very helpful, that said I still struggled (just a bit) to get this working. So, in case it helps someone I'll spell out the working solution:
XML from Original Question. The xml is in a file Class1.xml, a path to this file is used in the code to locate this xml file.
I used the answer by #erymski to get this working, so created a file called Car.cs and added the following:
using System.Xml.Serialization; // Added
public class Car
{
public string StockNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
[XmlRootAttribute("Cars")]
public class CarCollection
{
[XmlElement("Car")]
public Car[] Cars { get; set; }
}
The other bit of code provided by #erymski ...
using (TextReader reader = new StreamReader(path))
{
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
return (CarCollection) serializer.Deserialize(reader);
}
... goes into your main program (Program.cs), in static CarCollection XCar() like this:
using System;
using System.IO;
using System.Xml.Serialization;
namespace ConsoleApp2
{
class Program
{
public static void Main()
{
var c = new CarCollection();
c = XCar();
foreach (var k in c.Cars)
{
Console.WriteLine(k.Make + " " + k.Model + " " + k.StockNumber);
}
c = null;
Console.ReadLine();
}
static CarCollection XCar()
{
using (TextReader reader = new StreamReader(#"C:\Users\SlowLearner\source\repos\ConsoleApp2\ConsoleApp2\Class1.xml"))
{
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
return (CarCollection)serializer.Deserialize(reader);
}
}
}
}
Hope it helps :-)

Kevin's anser is good, aside from the fact, that in the real world, you are often not able to alter the original XML to suit your needs.
There's a simple solution for the original XML, too:
[XmlRoot("Cars")]
public class XmlData
{
[XmlElement("Car")]
public List<Car> Cars{ get; set; }
}
public class Car
{
public string StockNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
And then you can simply call:
var ser = new XmlSerializer(typeof(XmlData));
var data = (XmlData)ser.Deserialize(XmlReader.Create(PathToCarsXml));

One liner:
var object = (Cars)new XmlSerializer(typeof(Cars)).Deserialize(new StringReader(xmlString));

Try this Generic Class For Xml Serialization & Deserialization.
public class SerializeConfig<T> where T : class
{
public static void Serialize(string path, T type)
{
var serializer = new XmlSerializer(type.GetType());
using (var writer = new FileStream(path, FileMode.Create))
{
serializer.Serialize(writer, type);
}
}
public static T DeSerialize(string path)
{
T type;
var serializer = new XmlSerializer(typeof(T));
using (var reader = XmlReader.Create(path))
{
type = serializer.Deserialize(reader) as T;
}
return type;
}
}

How about a generic class to deserialize an XML document
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generic class to load any xml into a class
// used like this ...
// YourClassTypeHere InfoList = LoadXMLFileIntoClass<YourClassTypeHere>(xmlFile);
using System.IO;
using System.Xml.Serialization;
public static T LoadXMLFileIntoClass<T>(string xmlFile)
{
T returnThis;
XmlSerializer serializer = new XmlSerializer(typeof(T));
if (!FileAndIO.FileExists(xmlFile))
{
Console.WriteLine("FileDoesNotExistError {0}", xmlFile);
}
returnThis = (T)serializer.Deserialize(new StreamReader(xmlFile));
return (T)returnThis;
}
This part may, or may not be necessary. Open the XML document in Visual Studio, right click on the XML, choose properties. Then choose your schema file.

The idea is to have all level being handled for deserialization
Please see a sample solution that solved my similar issue
<?xml version="1.0" ?>
<TRANSACTION_RESPONSE>
<TRANSACTION>
<TRANSACTION_ID>25429</TRANSACTION_ID>
<MERCHANT_ACC_NO>02700701354375000964</MERCHANT_ACC_NO>
<TXN_STATUS>F</TXN_STATUS>
<TXN_SIGNATURE>a16af68d4c3e2280e44bd7c2c23f2af6cb1f0e5a28c266ea741608e72b1a5e4224da5b975909cc43c53b6c0f7f1bbf0820269caa3e350dd1812484edc499b279</TXN_SIGNATURE>
<TXN_SIGNATURE2>B1684258EA112C8B5BA51F73CDA9864D1BB98E04F5A78B67A3E539BEF96CCF4D16CFF6B9E04818B50E855E0783BB075309D112CA596BDC49F9738C4BF3AA1FB4</TXN_SIGNATURE2>
<TRAN_DATE>29-09-2015 07:36:59</TRAN_DATE>
<MERCHANT_TRANID>150929093703RUDZMX4</MERCHANT_TRANID>
<RESPONSE_CODE>9967</RESPONSE_CODE>
<RESPONSE_DESC>Bank rejected transaction!</RESPONSE_DESC>
<CUSTOMER_ID>RUDZMX</CUSTOMER_ID>
<AUTH_ID />
<AUTH_DATE />
<CAPTURE_DATE />
<SALES_DATE />
<VOID_REV_DATE />
<REFUND_DATE />
<REFUND_AMOUNT>0.00</REFUND_AMOUNT>
</TRANSACTION>
</TRANSACTION_RESPONSE>
The above XML is handled in two level
[XmlType("TRANSACTION_RESPONSE")]
public class TransactionResponse
{
[XmlElement("TRANSACTION")]
public BankQueryResponse Response { get; set; }
}
The Inner level
public class BankQueryResponse
{
[XmlElement("TRANSACTION_ID")]
public string TransactionId { get; set; }
[XmlElement("MERCHANT_ACC_NO")]
public string MerchantAccNo { get; set; }
[XmlElement("TXN_SIGNATURE")]
public string TxnSignature { get; set; }
[XmlElement("TRAN_DATE")]
public DateTime TranDate { get; set; }
[XmlElement("TXN_STATUS")]
public string TxnStatus { get; set; }
[XmlElement("REFUND_DATE")]
public DateTime RefundDate { get; set; }
[XmlElement("RESPONSE_CODE")]
public string ResponseCode { get; set; }
[XmlElement("RESPONSE_DESC")]
public string ResponseDesc { get; set; }
[XmlAttribute("MERCHANT_TRANID")]
public string MerchantTranId { get; set; }
}
Same Way you need multiple level with car as array
Check this example for multilevel deserialization

If you're getting errors using xsd.exe to create your xsd file, then use the XmlSchemaInference class as mentioned on msdn. Here's a unit test to demonstrate:
using System.Xml;
using System.Xml.Schema;
[TestMethod]
public void GenerateXsdFromXmlTest()
{
string folder = #"C:\mydir\mydata\xmlToCSharp";
XmlReader reader = XmlReader.Create(folder + "\some_xml.xml");
XmlSchemaSet schemaSet = new XmlSchemaSet();
XmlSchemaInference schema = new XmlSchemaInference();
schemaSet = schema.InferSchema(reader);
foreach (XmlSchema s in schemaSet.Schemas())
{
XmlWriter xsdFile = new XmlTextWriter(folder + "\some_xsd.xsd", System.Text.Encoding.UTF8);
s.Write(xsdFile);
xsdFile.Close();
}
}
// now from the visual studio command line type: xsd some_xsd.xsd /classes

You can just change one attribute for you Cars car property from XmlArrayItem to XmlElment. That is, from
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlArrayItem(typeof(Car))]
public Car[] Car { get; set; }
}
to
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
[XmlElement("Car")]
public Car[] Car { get; set; }
}

My solution:
Use Edit > Past Special > Paste XML As Classes to get the class in your code
Try something like this: create a list of that class (List<class1>), then use the XmlSerializer to serialize that list to a xml file.
Now you just replace the body of that file with your data and try to deserialize it.
Code:
StreamReader sr = new StreamReader(#"C:\Users\duongngh\Desktop\Newfolder\abc.txt");
XmlSerializer xml = new XmlSerializer(typeof(Class1[]));
var a = xml.Deserialize(sr);
sr.Close();
NOTE: you must pay attention to the root name, don't change it. Mine is "ArrayOfClass1"

Related

How to generate a C# class from an XML file without using Visual Studio GUI or command line but using C# code?

I want to convert the contents of an XML file into a C# class. Which NuGet package can I use, and how can I use them?
My question is that I want to create a class (e.g. Person) class from an XML file that already contains person information.
Example for XML file:
<?xml version="1.0" encoding="utf-8" ?>
<Person>
<Id>3344</Id>
<Name>Ahmed Shaikoun</Name>
<Title>Software Engineer</Title>
<Country>Egypt</Country>
<Phone>0xx-xxxxxxxxx</Phone>
</Person>
From this XML file, I want to generate a C# class with the name of class is Person by code (not Visual Studio tips or command line) using a library from the NuGet package.
Can you help me with this? Thank you.
I think you will need namespace "System.Xml.Serialization" to use class XmlSerializer because it has the functionality that you want that will serialize and deserialize to/from XML format to object against known type, like user defined class you will create.
Here is a quick example for you, suppose you have xml file named Person.xml and it is contains these data:
<?xml version="1.0" encoding="utf-8" ?>
<Person>
<Id>3344</Id>
<Name>Ahmed Shaikoun</Name>
<Title>Software Engineer</Title>
<Country>Egypt</Country>
<Phone>0xx-xxxxxxxxx</Phone>
</Person>
And you would like to create console application to just deal with this file to load data from it or to write data to it using built in functionality that support dealing with xml inside .NET framework, all you need first to define class type for this person like this:
public class Person
{
public string Id { get; set;}
public string Name { get; set;}
public string Title { get; set;}
public string Country { get; set;}
public string Phone { get; set;}
public override string ToString()
{
return $"{Name}, {Title}, Id: {Id}, from {Country}";
}
}
Then you will create a new class that will do the serialization/deserialization and parsing of course to load the deserialized object to memory:
public class XMLSerializer
{
public T Deserialize<T>(string input) where T : class
{
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (StringReader sr = new StringReader(input))
{
return (T)ser.Deserialize(sr);
}
}
public string Serialize<T>(T ObjectToSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(ObjectToSerialize.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, ObjectToSerialize);
return textWriter.ToString();
}
}
}
It is pretty easy, and last you can try it by running this easy example that i made here for you:
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
XMLSerializer serializer = new XMLSerializer();
// load file
string filePath = Directory.GetCurrentDirectory() + #"\Person.xml";
string xmlInputData = File.ReadAllText(filePath);
// deserialize and parse xml to person object
Person person = serializer.Deserialize<Person>(xmlInputData);
// display person object
Console.WriteLine($"Serialize and display person: {person.ToString()}");
// serialize person object to string content
string xmlOutputData = serializer.Serialize<Person>(person);
}
}
This is all you need without dealing manually with data and also it did not require any GUI to build.
And here the full example that I prepared here:
namespace XMLSerialize
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
XMLSerializer serializer = new XMLSerializer();
// load file
string filePath = Directory.GetCurrentDirectory() + #"\Person.xml";
string xmlInputData = File.ReadAllText(filePath);
// deserialize and parse xml to person object
Person person = serializer.Deserialize<Person>(xmlInputData);
// display person object
Console.WriteLine($"Serialize and display person: {person.ToString()}");
// serialize person object to string content
string xmlOutputData = serializer.Serialize<Person>(person);
}
}
public class Person
{
public string Id { get; set;}
public string Name { get; set;}
public string Title { get; set;}
public string Country { get; set;}
public string Phone { get; set;}
public override string ToString()
{
return $"{Name}, {Title}, Id: {Id}, from {Country}";
}
}
public class XMLSerializer
{
public T Deserialize<T>(string input) where T : class
{
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (StringReader sr = new StringReader(input))
{
return (T)ser.Deserialize(sr);
}
}
public string Serialize<T>(T ObjectToSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(ObjectToSerialize.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, ObjectToSerialize);
return textWriter.ToString();
}
}
}
}
XmlSerializer is defined in Assembly System.Xml.XmlSerializer, it is working with .NET 7 too.
I hope my little answer helping you to do this task.
Thank you

Casting data with a custom XML prefix to a C# model

I am trying to cast an XML document to a C# class, and for the most part, it's working fine, but in my XML I have a custom prefix, which gives me some trouble. I don't have any control over how the XML file is being generated.
<mxb:bike>Test Bike</mxb:bike>
I'm getting stuck on the mxb part since it isn't declared. My problem is that it's not declared in the XML file itself, since otherwise, it wouldn't throw the error in the first place, but I hope there is a way of solving this code side. I'm working with a .NET application, and am trying to parse an XML file to a C# model.
I'm getting the data through a post request on an API endpoint, which when I specify a model automatically converts it to that model.
My XML input is as following:
<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxacx="http://www.garmin.com/xmlschemas/AccelerationExtension/v1">
<metadata>
<time>2022-09-25T12:06:47.081Z</time>
<extensions>
<mxb:bike>Test Bike</mxb:bike>
<mxb:notes>Testing</mxb:notes>
<mxb:conditions>Dry</mxb:conditions>
<mxb:track>Test Track</mxb:track>
</extensions>
</metadata>
<trk>
<trkseg>
<trkpt lat="52.914892" lon="4.776444">
<time>2022-09-25T12:06:47.081Z</time>
</trkpt>
</trkseg>
</trk>
</gpx>
My C# model looks like this:
public class GpxData
{
[XmlRoot(ElementName="extensions", Namespace="http://www.topografix.com/GPX/1/1")]
public class Extensions {
[XmlElement(ElementName="bike", Namespace="")]
public string? Bike { get; set; }
[XmlElement(ElementName="notes", Namespace="")]
public string? Notes { get; set; }
[XmlElement(ElementName="conditions", Namespace="")]
public string? Conditions { get; set; }
[XmlElement(ElementName="track", Namespace="")]
public string? Track { get; set; }
}
[XmlRoot(ElementName="metadata", Namespace="http://www.topografix.com/GPX/1/1")]
public class Metadata {
[XmlElement(ElementName="time", Namespace="http://www.topografix.com/GPX/1/1")]
public DateTime Time { get; set; }
[XmlElement(ElementName="extensions", Namespace="http://www.topografix.com/GPX/1/1")]
public Extensions Extensions { get; set; }
}
[XmlRoot(ElementName = "trkpt", Namespace = "http://www.topografix.com/GPX/1/1")]
public class Trkpt
{
[XmlElement(ElementName = "time", Namespace = "http://www.topografix.com/GPX/1/1")]
public DateTime Time { get; set; }
[XmlAttribute(AttributeName = "lat", Namespace = "")]
public double Lat { get; set; }
[XmlAttribute(AttributeName = "lon", Namespace = "")]
public double Lon { get; set; }
}
[XmlRoot(ElementName = "trkseg", Namespace = "http://www.topografix.com/GPX/1/1")]
public class Trkseg
{
[XmlElement(ElementName = "trkpt", Namespace = "http://www.topografix.com/GPX/1/1")]
public List<Trkpt> Trkpt { get; set; }
}
[XmlRoot(ElementName = "trk", Namespace = "http://www.topografix.com/GPX/1/1")]
public class Trk
{
[XmlElement(ElementName = "trkseg", Namespace = "http://www.topografix.com/GPX/1/1")]
public Trkseg Trkseg { get; set; }
}
[XmlRoot(ElementName = "gpx", Namespace = "http://www.topografix.com/GPX/1/1")]
public class Gpx
{
[XmlElement(ElementName="metadata", Namespace="http://www.topografix.com/GPX/1/1")]
public Metadata Metadata { get; set; }
[XmlElement(ElementName = "trk", Namespace = "http://www.topografix.com/GPX/1/1")]
public Trk Trk { get; set; }
[XmlAttribute(AttributeName = "creator", Namespace = "")]
public string Creator { get; set; }
}
Whenever I remove the part within the "extensions" tag, it works completely fine.
When I only remove the mxb part, and just keep the part with for example "bike" it doesn't work either.
I tried to declare the MXB prefix in the XML file itself, but since I don't have any control over the XML generation, I can't really change anything within the file, since that would only be a temporary solution, besides the fact that I don't have a schema, and as far as I know, I can't declare a prefix, without giving it a schema. Even if that would work, that wouldn't be a good solution in my case. I also tried to remove the mxb part in the XML file, and even though that solved the undeclared error issue, it would just result in the information not being cast to the model.
All the solutions I tried were related to changing something in the XML file, but that's not something I can do ;-(
I'm pretty new to XML serialzation \ deseralation, so I hope someone is able to help me! Thanks in advance.
Inspired from this answer.
You can inject a custom XmlNamespaceManager to correct missing namespace.
public class MyXmlNamespaceManager : XmlNamespaceManager
{
const string MissingNamespacePrefix = "http://missing.namespace.prefix.net/2014/";
public MyXmlNamespaceManager(XmlNameTable nameTable)
: base(nameTable)
{ }
void AddMissingNamespace(string prefix)
{
if (string.IsNullOrEmpty(prefix))
return;
string uri = MissingNamespacePrefix + prefix;
AddNamespace(prefix, uri);
}
public override bool HasNamespace(string prefix)
{
var result = base.HasNamespace(prefix);
if (!result)
AddMissingNamespace(prefix);
result = base.HasNamespace(prefix);
return result;
}
public override string LookupNamespace(string prefix)
{
var result = base.LookupNamespace(prefix);
if (result == null)
AddMissingNamespace(prefix);
result = base.LookupNamespace(prefix);
return result;
}
}
Thereby, the extension node become :
<extensions>
<mxb:bike xmlns:mxb="http://missing.namespace.prefix.net/2014/mxb">Test Bike</mxb:bike>
<mxb:notes xmlns:mxb="http://missing.namespace.prefix.net/2014/mxb">Testing</mxb:notes>
<mxb:conditions xmlns:mxb="http://missing.namespace.prefix.net/2014/mxb">Dry</mxb:conditions>
<mxb:track xmlns:mxb="http://missing.namespace.prefix.net/2014/mxb">mxb Track</mxb:track>
</extensions>
You need to adapt your model class like :
[XmlRoot(ElementName = "extensions", Namespace = "http://www.topografix.com/GPX/1/1")]
public class Extensions
{
[XmlElement(ElementName = "bike", Namespace = "http://missing.namespace.prefix.net/2014/mxb")]
public string? Bike { get; set; }
[XmlElement(ElementName = "notes", Namespace = "http://missing.namespace.prefix.net/2014/mxb")]
public string? Notes { get; set; }
[XmlElement(ElementName = "conditions", Namespace = "http://missing.namespace.prefix.net/2014/mxb")]
public string? Conditions { get; set; }
[XmlElement(ElementName = "track", Namespace = "http://missing.namespace.prefix.net/2014/mxb")]
public string? Track { get; set; }
}
Then deserialize like :
var xml = "...";
// Fix the Xml
XmlDocument xmlDoc;
using (var stream = new StringReader(xml))
{
var settings = new XmlReaderSettings();
settings.NameTable = new NameTable();
var manager = new MyXmlNamespaceManager(settings.NameTable);
XmlParserContext context = new XmlParserContext(null, manager, null, XmlSpace.Default);
using (var xmlReader = XmlReader.Create(stream, settings, context))
{
xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
}
}
// Deserialize
using (var stream = new MemoryStream())
{
xmlDoc.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
var serializer = new XmlSerializer(typeof(GpxData.Gpx));
var result = serializer.Deserialize(stream) as GpxData.Gpx;
Console.WriteLine(result.Metadata.Extensions.Bike);
}

Deserialize an XML file to object in C#

I tried to Deserialize an XML to an object using C#, but I am getting some errors.
My XML file is:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:transmission xmlns:ns0="blabla.xsd">
<ns0:DiagnosisErrorResponse>
<ns0:ID>7</ns0:ID>
<ns0:ErrorCode>9</ns0:ErrorCode>
<ns0:ErrorDescription>sometext</ns0:ErrorDescription>
<ns0:ErrorDate>11-12-2018</ns0:ErrorDate>
</ns0:DiagnosisErrorResponse>
</ns0:transmission>
I have an entity like this:
[XmlRoot(Namespace = "blabla.xsd", ElementName = "ns0:transmission", DataType = "string", IsNullable = true)]
public class DiagnosisErrorResponse
{
[XmlElement("ID")]
public long ID { get; set; }
[XmlElement("ErrorCode")]
public int ErrorCode { get; set; }
[XmlElement("ErrorDescription")]
public string ErrorDescription { get; set; }
[XmlElement("ErrorDate")]
public string ErrorDate { get; set; }
}
and my error is:
<transmission xmlns='blabla.xsd'> was not expected.
My function is this:
private void ReadXmlFileByPath(string filePath)
{
string xmlText = string.Empty;
XmlDocument d = new XmlDocument();
using (XmlTextReader tr = new XmlTextReader(filePath))
{
tr.Namespaces = true;
d.Load(tr);
xmlText = d.InnerXml;
}
XmlSerializer xs = new XmlSerializer(typeof(DiagnosisErrorResponse), "");
using (TextReader reader = new StringReader(xmlText))
{
DiagnosisErrorResponse result = (DiagnosisErrorResponse)xs.Deserialize(reader);
}
}
Update: I removed the 'ns0:' prefix from the XmlElement attribute at entity but I still get the same error.
Your problem is that your c# data model does not match your XML. Specifically:
You have indicated that the element name for DiagnosisErrorResponse should be "ns0:transmission". The namespace prefix ns0: should not be included, it is just a lookup in the XML file to find the actual namespace, which is defined by the xmlns attribute xmlns:ns0="blabla.xsd".
The XML has a level of nesting not accounted for in your data model. The XML in your question is not indented; if I indent it, it looks like:
<ns0:transmission xmlns:ns0="blabla.xsd">
<ns0:DiagnosisErrorResponse>
<ns0:ID>7</ns0:ID>
<ns0:ErrorCode>9</ns0:ErrorCode>
<ns0:ErrorDescription>sometext</ns0:ErrorDescription>
<ns0:ErrorDate>11-12-2018</ns0:ErrorDate>
</ns0:DiagnosisErrorResponse>
</ns0:transmission>
As can now be seen, ID and so on are not children of transmission, they are children of DiagnosisErrorResponse which is a child of transmission. This needs to be accounted for in your c# classes.
Combining these two issues, your data model should look like:
[XmlRoot(Namespace = "blabla.xsd", ElementName = "transmission", IsNullable = true)]
public class Transmission
{
[XmlElement("DiagnosisErrorResponse")]
public DiagnosisErrorResponse DiagnosisErrorResponse { get; set; }
}
[XmlRoot(Namespace = "blabla.xsd", IsNullable = true)]
public class DiagnosisErrorResponse
{
[XmlElement("ID")]
public long ID { get; set; }
[XmlElement("ErrorCode")]
public int ErrorCode { get; set; }
[XmlElement("ErrorDescription")]
public string ErrorDescription { get; set; }
[XmlElement("ErrorDate")]
public string ErrorDate { get; set; }
}
And your deserialization code should look like:
private DiagnosisErrorResponse ReadXmlFileByPath(string filePath)
{
using (var xmlReader = XmlReader.Create(filePath))
{
XmlSerializer xs = new XmlSerializer(typeof(Transmission));
var result = (Transmission)xs.Deserialize(xmlReader);
return result.DiagnosisErrorResponse;
}
}
Demo fiddle #1 here.
Alternatively, if you were trying to deserialize just the <DiagnosisErrorResponse> element inside the XML file without creating a data model for the root element, you could use XmlReader to scan forward in the XML until the appropriate element is found:
private DiagnosisErrorResponse ReadXmlFileByPath(string filePath)
{
using (var xmlReader = XmlReader.Create(filePath))
{
if (!xmlReader.ReadToDescendant("DiagnosisErrorResponse", "blabla.xsd"))
return null;
XmlSerializer xs = new XmlSerializer(typeof(DiagnosisErrorResponse));
var result = (DiagnosisErrorResponse)xs.Deserialize(xmlReader);
return result;
}
}
Demo fiddle #2 here.
Notes:
There is no need to load the XML file into an XmlDocument, re-serialize it as a string, then deserialize the string. The XML can be deserialized directly from a file in one step using an XmlReader or StreamReader.
XmlTextReader has been deprecated by Microsoft in its documentation. XmlReader.Create() should be used instead.
You can avoid modeling errors such as the ones above by using one of the code generation tools mentioned in Generate C# class from XML.
The easiest way to diagnose an error in XML deserialization is to serialize an instance of your root type, then compare the resulting XML with the XML to be deserialized. Inconsistencies are typically easily spotted and indicate where you have problems.

Have both DataType and additional Attribute during Xml Serialization

I am attempting to create an XML document through the application of attributes on fields/properties ([XmlAttribute], [XmlElement], etc.) My problem is that I have a requirement that I attach an additional attribute to a primitive datatype in the style of:
<document xmlns:dt="urn:schemas-microsoft-com:datatypes" >
<binary addAttribute="X" dt:dt="bin.base64">
[... binary ...]
</binary>
</document>
I'm making use of code like the following:
[Serializable]
public class Document {
[XmlElement]
public BinaryObject Binary { get; set; }
}
[Serializable]
public class BinaryObject {
[XmlText(DataType = "base64Binary")]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
}
public class XmlExample {
public static void Main(string[] args)
{
Document document = new Document();
document.Binary = new BinaryObject();
document.Binary.Binary = File.ReadAllBytes(#"FileName");
document.Binary.AddAttribute = 0;
XmlSerializer serializer = new XmlSerializer(typeof(Document));
serializer.Serialize(Console.Out, document);
Console.ReadLine();
}
}
This, however, provides the following output:
<document>
<binary addAttribute="X">
[... binary ...]
</binary>
</document>
If I attempt to move the byte[] Binary to the Document class instead I can get the xmlns:dt="..." as expected but I cannot attach the arbitrary addAttribute when I do so (unless I missed something obvious.) This was incorrect; I misread something in the XML that I was getting out of the XML. The xmlns:dt element was not added in this case.
The question is: Can I do this (have both the DataType and the addAttribute) exclusively through C# attributes?
The answer to this question came partially from here: XmlSerializer attribute namespace for element type dt:dt namespace. The DataType = "base64Binary" does not apply the xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="bin.base64" to the element that it is attached to. An attribute has to be added to the BinaryObject that provides the dt:dt = "bin.base64" with the correct name space.
Final Code
[Serializable]
public class Document {
public BinaryObject Binary { get; set; }
}
[Serializable]
public class BinaryObject {
[XmlText]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
// Adds the dt:dt object to the correct name space.
[XmlAttribute("dt", Namespace = "urn:schemas-microsoft-com:datatypes")]
public string DataType { get; set; }
public BinaryObject() { DataType = "bin.base64"; }
}
public class XmlExample {
public static void Main(string[] args)
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
// Adds the needed namespace to the document.
namespaces.Add("dt", "urn:schemas-microsoft-com:datatypes");
Document document = new Document();
document.Binary = new BinaryObject();
document.Binary.Binary = new byte[]{0,1,2,3,4,5,6,7,8,9};
document.Binary.AddAttribute = 0;
XmlSerializer serializer = new XmlSerializer(typeof(Document));
serializer.Serialize(Console.Out, document, namespaces);
Console.ReadLine();
}
}
Final Output
<Document xmlns:dt="urn:schemas-microsoft-com:datatypes">
<Binary AddAttribute="0" dt:dt="bin.base64">AAECAwQFBgcICQ==</Binary>
</Document>
Try this
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
[XmlRoot("Document")]
public class Document
{
[XmlText(DataType = "base64Binary")]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
}
}
​

XML Deserialization

I have the following xml file.
<a>
<b>
<c>val1</c>
<d>val2</d>
</b>
<b>
<c>val3</c>
<d>val4</d>
</b>
<a>
I want to deserialize this into a class and I want to access them with the objects of the class created. I am using C#. I am able to deserialize and get the value into the object of class ‘a’ (the <a> tag). but how to access the value of <b> from this object?
I did the following coding:
[Serializable()]
[XmlRoot("a")]
public class a
{
[XmlArray("a")]
[XmlArrayItem("b", typeof(b))]
public b[] bb{ get; set; }
}
[Serializable()]
public class b
{
[XmlElement("c")]
public string c{ get; set; }
[XmlElement("d")]
public string d{ get; set; }
}
class Program
{
static void Main(string[] args)
{
a i = null;
string path = "test.xml";
XmlSerializer serializer = new XmlSerializer(typeof(a));
StreamReader reader = new StreamReader(path);
i = (a)serializer.Deserialize(reader);
reader.Close();
//i want to print all b tags here
Console.Read();
}
}
For this to work you can make the following change
public class a
{
[XmlElement("b")]
public b[] bb{ get; set; }
}
By using the XmlElement attribute on the array, you are essentially telling the serializer that the array elements should be serialize/deserialized as direct child elements of the current element.
Here is a working example, I put the XML in a string just to make the example self contained.
using System;
using System.IO;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
#"<a>
<b>
<c>val1</c>
<d>val2</d>
</b>
<b>
<c>val3</c>
<d>val4</d>
</b>
</a>";
XmlSerializer xs = new XmlSerializer(typeof(a));
a i = (a)xs.Deserialize(new StringReader(xml));
if (i != null && i.bb != null && i.bb.Length > 0)
{
Console.WriteLine(i.bb[0].c);
}
else
{
Console.WriteLine("Something went wrong!");
}
Console.ReadKey();
}
}
[XmlRoot("a")]
public class a
{
[XmlElement("b")]
public b[] bb { get; set; }
}
public class b
{
[XmlElement("c")]
public string c { get; set; }
[XmlElement("d")]
public string d { get; set; }
}
}
When in doubt with creating your xml serialization classes, i find the easiest way to solve the problem is to:
dump all your dummy data into an XML file
run xsd.exe to create a .xsd schema file
run xsd.exe on your schema file to create a class file
i wrote a quick tutorial on it in a blog post a while ago:
http://www.diaryofaninja.com/blog/2010/05/07/make-your-xml-stronglytyped-because-you-can-and-its-easy
it takes less than a minute and you can then easily tweak things from there. XSD.exe is your friend
I find the easiest way to solve the problem is to change your definition of classes a and b to the following
public class b {
public string c { get; set; }
public string d { get; set; }
}
[XmlRoot(Namespace="", ElementName="a")]
public class a : List<b> { }
then your program will work. Optionally you can add to the class b the attribute [XmlRoot (Namespace = "", ElementName = "b")]
class Program
{
static void Main(string[] args)
{
string employeedata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><tag><name>test</bar></nmae>";//demo xml data
using (TextReader sr = new StringReader(employeedata))
{
XmlSerializer serializer = new XmlSerializer(typeof(Employee));//pass type name in XmlSerializer constructor here
Employee response = (Employee)serializer.Deserialize(sr);
Console.WriteLine(response.name);
}
}
}
[System.Xml.Serialization.XmlRoot("tag")]
public class Employee
{
public string name { get; set; }
}
Change if block (from Chris Tyler's answer) like below.
if (i != null && i.bb != null && i.bb.Length > 0)
{
foreach (b t in i.bb)
{
Console.WriteLine(t.c);
Console.WriteLine(t.d);
}
}
then it will give you below result:
val1
val2
val3
val4

Categories