C# parse XML file to object - c#

Using C#, is there a way to easily parse an XML file so that it can be used as an object?
Example XML:
<Config>
<Ui>
<Colour>black</Colour>
<Size>small</Size>
</Ui>
<Output>
<Mode>smb</Mode>
<Version>2</Version>
</Output>
</Config>
And then refer to the parameters in my application by
Config.Output.Mode
I've tried this method - How to Deserialize XML document
But when I try
var cfg = new Config();
cfg.Load(#"config.xml");
Console.WriteLine(cfg.Output.Mode);
visual studio indicates .Output.Mode is not valid.
Where Config.Load is
xmlData = File.ReadAllText(configPath);
var serializer = new XmlSerializer(typeof(Config));
using (var reader = new StringReader(xmlData))
{
Config result = (Config)serializer.Deserialize(reader);
}

You have to create the classes that match the definition in the xml file in order to deserialize the file into an instance of the class. Note that I've named the properties with the same name as we have in the xml file. If you want to use different property names, then you'd need to add an attribute above the property that specifies the xml element that should map to it (like for the Ui, you would add the attribute: [XmlElement("Ui")]).
Note that I've also overridden the ToString methods for the classes so we can output them to the console in a nice fashion:
public class Config
{
public UI Ui { get; set; }
public Output Output { get; set; }
public override string ToString()
{
return $"Config has properties:\n - Ui: {Ui}\n - Output: {Output}";
}
}
public class UI
{
public string Colour { get; set; }
public string Size { get; set; }
public override string ToString()
{
return $"(Colour: {Colour}, Size: {Size})";
}
}
public class Output
{
public string Mode { get; set; }
public int Version { get; set; }
public override string ToString()
{
return $"(Mode: {Mode}, Version: {Version})";
}
}
Now all we have to do is create a StreamReader, point it to our file path, and then use the XmlSerializer class to Deserialize the file (casting the output to the appropriate type) into an object:
static void Main(string[] args)
{
var filePath = #"f:\private\temp\temp2.txt";
// Declare this outside the 'using' block so we can access it later
Config config;
using (var reader = new StreamReader(filePath))
{
config = (Config) new XmlSerializer(typeof(Config)).Deserialize(reader);
}
Console.WriteLine(config);
GetKeyFromUser("\n\nDone! Press any key to exit...");
}
Output

Here are the classes:
public class Config
{
public UI UI { get; set; }
public Output Output { get; set; }
}
public struct UI
{
public string Colour { get; set; }
public string Size { get; set; }
}
public struct Output
{
public string Mode { get; set; }
public int Version { get; set; }
}
The Deserialize function:
public static T Deserialize<T>(string xmlString)
{
if (xmlString == null) return default;
var serializer = new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T) serializer.Deserialize(reader);
}
}
And here's a working version:
Config cfg = Deserialize<Config>(xmlString);
Console.WriteLine(cfg.Output.Mode);

Related

Null value on xml deserialization using [XmlAttribute]

I have the following XML;
<?xml version="1.0" encoding="UTF-8" ?>
<feedback>
<report_metadata>
<org_name>example.com</org_name>
</report_metadata>
</feedback>
and the following Feedback.cs class;
[XmlRoot("feedback", Namespace = "", IsNullable = false)]
public class Feedback
{
[XmlElement("report_metadata")]
public MetaData MetaData { get; set; }
}
[XmlType("report_metadata")]
public class MetaData
{
[XmlAttribute("org_name")]
public string Organisation { get; set; }
}
When I attempt to deserialize, the value for Organisation is null.
var xml = System.IO.File.ReadAllText("example.xml");
var serializer = new XmlSerializer(typeof(Feedback));
using (var reader = new StringReader(input))
{
var feedback = (Feedback)serializer.Deserialize(reader);
}
Yet, when I change Feedback.cs to the following, it works (obviously the property name has changed).
[XmlType("report_metadata")]
public class MetaData
{
//[XmlAttribute("org_name")]
public string org_name { get; set; }
}
I want the property to be Organisation, not org_name.
In the example XML file org_name is an XML element, not an XML attribute. Changing
[XmlAttribute("org_name")] to [XmlElement("org_name")] at the Organisation property will deserialize it as an element:
[XmlElement("org_name")]
public string Organisation { get; set; }
probably just typo
[XmlAttribute("org_name")]
public string Organisation { get; set; }
was supposed to be
[XmlElement("org_name")]
public string Organisation { get; set; }
Try to modify your Xml classes like
[XmlRoot(ElementName = "report_metadata")]
public class MetaData
{
[XmlElement(ElementName = "org_name")]
public string Organisation { get; set; }
}
[XmlRoot(ElementName = "feedback")]
public class Feedback
{
[XmlElement(ElementName = "report_metadata")]
public MetaData MetaData { get; set; }
}
Then you will get your desired output like
class Program
{
static void Main(string[] args)
{
Feedback feedback = new Feedback();
var xml = System.IO.File.ReadAllText(#"C:\Users\Nullplex6\source\repos\ConsoleApp4\ConsoleApp4\Files\XMLFile1.xml");
var serializer = new XmlSerializer(typeof(Feedback));
using (var reader = new StringReader(xml))
{
feedback = (Feedback)serializer.Deserialize(reader);
}
Console.WriteLine($"Organization: {feedback.MetaData.Organisation}");
Console.ReadLine();
}
}
Output:

Deserialize FontAwesome Yaml Using YamlDotNet

I have a Yaml file:
https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/src/icons.yml
And a class:
public class IconSearch
{
public string Name { get; set; }
public string ClassName { get; set; }
public IEnumerable<string> Filters { get; set; }
}
Can you tell me how I can deserialize the yaml to an IEnumerable of objects?
I expect something like this to work, but it returns null - I'm guessing it's because one of my properties is not the root node (icons). Instead, I'm trying to serialize the children of the root?
var input = new StringReader(reply);
var yaml = new YamlStream();
yaml.Load(input);
var icons = deserializer.Deserialize<IconSearch>(input);
The class you are trying to deserialize to seems to be missing properties.
I went the round about way of converting yaml to json to csharp and this is class that was generated:
public class Rootobject
{
public Icon[] icons { get; set; }
}
public class Icon
{
public string[] categories { get; set; }
public object created { get; set; }
public string[] filter { get; set; }
public string id { get; set; }
public string name { get; set; }
public string unicode { get; set; }
public string[] aliases { get; set; }
public string[] label { get; set; }
public string[] code { get; set; }
public string url { get; set; }
}
Resources used :
YAML to JSON online
JSON to CSHARP (I used Paste special in visual studio)
Use this to deserialize
var icons = deserializer.Deserialize<RootObject>(input);
Update
I have commented out the line that you use to create YamlStream as it is not required (it positions the reader to the end of the stream instead of the beginning, which would explain why you were getting null earlier). Your main method looks as follows and works. I have also fixed the bug that Antoine mentioned
public static void Main()
{
string filePath = "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/src/icons.yml";
WebClient client = new WebClient();
string reply = client.DownloadString(filePath);
var input = new StringReader(reply);
//var yamlStream = new YamlStream();
//yamlStream.Load(input);
Deserializer deserializer = new Deserializer();
//var icons = deserializer.Deserialize<IconSearch>(input);
//Testing my own implementation
//if (icons == null)
// Console.WriteLine("Icons is null");
//Testing Shekhar's suggestion
var root = deserializer.Deserialize<Rootobject>(input);
if (root == null)
Console.WriteLine("Root is null");
}

Deserialization of properties won't work, but works in TestProject and Generic-Lists

i have a rally weird problem with the deserialization of my ApplicationSettings-Class.
The class looks like following:
[Serializable]
public class ApplicationSettings
{
public string SelectedGeneralSetting { get; set; }
public string SelectedCheckSetting { get; set; }
public string SelectedDataBaseSetting { get; set; }
public string SelectedCompareSetting { get; set; }
public List<GeneralSetting> GeneralSettings = new List<GeneralSetting>();
public List<CheckSetting> CheckSettings = new List<CheckSetting>();
public List<DataBaseSetting> DataBaseSettings = new List<DataBaseSetting>();
public List<CompareSetting> CompareSettings = new List<CompareSetting>();
public ApplicationSettings()
{
}
}
The serialization of this class just works fine, but deserialization won't work, the public properties SelectedGeneralSetting, SelectedCheckSetting, SelectedDataBaseSetting and SelectedCompareSetting are null. I'm deserializing using this method:
private void deserialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(ApplicationSettings));
FileStream file = new FileStream(ApplicationSettingsPath + #"\settings.xml", FileMode.Open);
ApplicationSettings = (serializer.Deserialize(file) as ApplicationSettings);
file.Close();
}
There are two strange things, the Lists contain also a lot of properties and they are deserilized correctly. I created a test-project, containing a public member and property and a generic list, all were successfully deserialized. I really have no Idea why it shouldn't work in my "main-project". Does anyone have an advice? How can the deserialization behave so weird?
Greetings!
This code works for me.
private void Form1_Load(object sender, EventArgs e)
{
XmlSerializer serializer = new XmlSerializer(typeof(ApplicationSettings));
XmlReader reader = XmlReader.Create(new StringReader(
#"<?xml version=""1.0""?> <ApplicationSettings>
<SelectedGeneralSetting>Default</SelectedGeneralSetting>
<GeneralSettings>
<GeneralSetting><Name>DDD</Name></GeneralSetting>
</GeneralSettings></ApplicationSettings>"));
var result = (serializer.Deserialize(reader) as ApplicationSettings);
}
[Serializable]
public class ApplicationSettings
{
public string SelectedGeneralSetting { get; set; }
public List<GeneralSetting> GeneralSettings = new List<GeneralSetting>();
}
public class GeneralSetting
{
public string Name { get; set; }
}
Check [BR] text in your sample text
Did you miss the variable name in deserialize()?
It should be:
ApplicationSettings settings = (serializer.Deserialize(file) as ApplicationSettings);

adding and removing items in xml list

I have created a little logg program and with this i can save custom classes to xml, and convert them back from xml to a class. this works fine, but the problem is if i want to add one class to the list in the xml, i have to read all of them, add one class and rewrite all of them if i use this method, now i know i can manualy add a class by searching for elements and so on, but i wondered if i could do this in the way that i write all of them.
this is an example of the code that i ame using:
public static void Test()
{
List<LoggInformation> infos = new List<LoggInformation>();
infos.Add(new LoggInformation() { Level = BpuInterface.BpuInterface.BPUController.LoggLevel.Debug, Message = "error1" });
infos.Add(new LoggInformation() { Level = BpuInterface.BpuInterface.BPUController.LoggLevel.Error, Message = "error2" });
DataContractSerializer dd = new DataContractSerializer(typeof(List<LoggInformation>));
using (var writer = new StreamWriter(#"C://testLoggfile.xml"))
{
dd.WriteObject(writer.BaseStream, infos);
}
}
public static void AddOneItem()
{
//??????????
}
[DataContract]
public class LoggInformation
{
[DataMemberAttribute]
public BpuInterface.BpuInterface.BPUController.LoggLevel Level { get; set; }
[DataMemberAttribute]
public Source Source { get; set; }
[DataMemberAttribute]
public string ExceptionMessage { get; set; }
[DataMemberAttribute]
public string ExceptionStack { get; set; }
[DataMemberAttribute]
public string ThreadName { get; set; }
[DataMemberAttribute]
public System.Threading.ApartmentState ThreadApartmentState { get; set; }
[DataMemberAttribute]
public string Message { get; set; }
[DataMemberAttribute]
public DateTime DateTime { get; set; }
}
You can use this instead new StreamWriter(#"C://testLoggfile.xml"), true) will append line at the end of file.
public static void Test()
{
...
using (var writer = new StreamWriter(#"C://testLoggfile.xml"), true) // this will append line at the end of file.
{
dd.WriteObject(writer.BaseStream, infos);
}
}
...

Array deserialization only returning 1 element

As the title says when i deserialize the following file i only get the first (and always the first) element:
<?xml version="1.0"?>
<ServerConnections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Entries>
<ServerConnectionEntry>
<Name>Local</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
<ServerConnectionEntry>
<Name>Local2</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
</Entries>
</ServerConnections>
My code to deserialize:
var list = (ServerConnections)mSerializer.Deserialize(inputStream)).Entries;
and list.Count then is 1. ServerConnections looks like that:
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
There is no exception happening.
Edit:
The problem occurs when I include my class that does custom xml serialization (implements IXmlSerializable). What it does is the following:
void BigNumber::ReadXml(System::Xml::XmlReader^ reader) {
reader->ReadStartElement();
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
cli::array<Byte>^ data = (cli::array<Byte>^)serializer->Deserialize(reader);
pin_ptr<unsigned char> ptr(&data[0]);
BN_bin2bn(ptr, data->Length, mNumber);
}
void BigNumber::WriteXml(System::Xml::XmlWriter^ writer) {
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
serializer->Serialize(writer, ToByteArray());
}
While data contains the correct data after ReadXml the deserializer that works the whole list stops and does not read any additional elements.
Same here, this seems to work fine for me using code similar to yours.
public class Program
{
static void Main(string[] args)
{
XmlSerializer deserializer = new XmlSerializer(typeof(ServerConnections));
var reader = new StreamReader(#"../../Test.xml");
var entries = (ServerConnections)deserializer.Deserialize(reader);
reader.Close();
}
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
public class ServerConnectionEntry
{
public string Name { get; set; }
public string Host { get; set; }
public string Port { get; set; }
public string Username { get; set; }
public BinaryCode AuthHash { get; set; }
}
public class BinaryCode
{
[XmlElement("base64Binary")]
public string Code { get; set; }
}
}
I do not see any issue. I've even reproduced your scenario (complete test enclosed below) of your code, and it is doing its job correctly.
Try to search elsewhere (e.g. assure that the passed xml is the one you are expecting). But serialization is working correctly with your C# class mapping
EDIT: AuthHash class no does the conversion for you from byte[] to base64 and back
public class ServerConnections
{
public ServerConnectionEntry[] Entries { get; set; }
}
public class ServerConnectionEntry
{
public string Name { get; set; }
public AuthHash AuthHash { get; set; }
}
public class AuthHash
{
[XmlIgnore]
public byte[] Hash { get; set; }
public string base64Binary
{
get { return Convert.ToBase64String(Hash); }
set { Hash = Convert.FromBase64String(value); }
}
}
[TestClass]
public class DeserializationTest
{
public const string MyXml = #"<?xml version=""1.0""?>
<ServerConnections
xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Entries>
<ServerConnectionEntry>
<Name>Local</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
<ServerConnectionEntry>
<Name>Local2</Name>
<Host>127.0.0.1</Host>
<Port>15556</Port>
<Username>TestUser</Username>
<AuthHash>
<base64Binary>u7a0NN4uOvCrb5t5UWVVEl14Ygo=</base64Binary>
</AuthHash>
</ServerConnectionEntry>
</Entries>
</ServerConnections>
";
[TestMethod]
public void Deserialization_Has_Two_Elements()
{
TextReader reader = new StringReader(MyXml);
var mySerializer = new XmlSerializer(typeof(ServerConnections));
var list = ((ServerConnections)mySerializer.Deserialize(reader)).Entries;
Assert.IsTrue(list.Count() == 2);
Assert.IsTrue(list.First().Name == "Local");
Assert.IsTrue(list.Last().Name == "Local2");
Assert.IsTrue(list.First().AuthHash.Hash.Length > 0);
Assert.IsTrue(list.Last().AuthHash.Hash.Length > 0);
}
}
Well fizzlesticks, the problem was that i forgot one tiny little line in the deserialization. It should be like that:
void BigNumber::ReadXml(System::Xml::XmlReader^ reader) {
reader->ReadStartElement();
XmlSerializer^ serializer = gcnew XmlSerializer(cli::array<Byte>::typeid);
cli::array<Byte>^ data = (cli::array<Byte>^)serializer->Deserialize(reader);
pin_ptr<unsigned char> ptr(&data[0]);
BN_bin2bn(ptr, data->Length, mNumber);
reader->ReadEndElement();
}
The ReadEndElement makes sure it advances to the next node. As i didn't do that the deserializer above had a problem but instead of throwing an exception it just stops parsing and returns what it got so far...

Categories

Resources