I'm trying to set up a very small database using XML serialization and more specifically XmlSerializer.
My main class is the following :
public class XmlDB
{
[XmlIgnore]
public string FilePath { get; private set; }
public List<FooType> Foos { get; set; }
public List<BarType> Bars { get; set; }
public List<ThirdType> Thirds { get; set; }
private XmlDB():this(null) { }
public XmlDB(string strDBPath) {
this.FilePath = strDBPath;
this.Foos = new List<FooType>();
this.Bars = new List<BarType>();
this.Thirds = new List<ThirdType>();
}
public static XmlDB Load(string strDBPath) {
using (XmlReader reader = XmlReader.Create(strDBPath)) {
XmlDB db = (XmlDB)new XmlSerializer(typeof(XmlDB)).Deserialize(reader);
db.FilePath = strDBPath;
return db;
}
}
public void SaveChanges() {
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create(this.FilePath, settings)) {
XmlSerializer ser = new XmlSerializer(typeof(XmlDB));
ser.Serialize(writer, this);
}
}
}
My test method creates an instance, populates the lists and calls the SaveChanges method.
Everything works fine on serialization and the Xml output looks consistent.
The problem happens on deserializing : No error is reported but only the first item of the first List is treated, the following items of the first list are not deserialized, neither are the following lists...
If I shuffle the order of the lists in the Xml, it's always the first item of the first list in the Xml file that is deserialized.
I tried the following simple test to confirm (which unfortunately works fine, all lists are populated on deserializing) :
public class DBTestList
{
public List<DBTest> TestList { get; set; }
public List<DBTest2> TestList2 { get; set; }
public DBTestList() {
this.TestList = new List<DBTest>();
this.TestList2 = new List<DBTest2>();
}
}
public class DBTest
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public class DBTest2
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public void TestSerialProblem() {
//Init data
DBTestList tl = new DBTestList();
tl.TestList.Add(new DBTest() { TestInt = 1, TestStr = "test11" });
tl.TestList.Add(new DBTest() { TestInt = 2, TestStr = "test12" });
tl.TestList2.Add(new DBTest2() { TestInt = 3, TestStr = "test21" });
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create("test.db", settings)) {
XmlSerializer ser = new XmlSerializer(typeof(DBTestList));
ser.Serialize(writer, tl);
}
using (XmlReader reader = XmlReader.Create("test.db")) {
DBTestList db = (DBTestList)new XmlSerializer(typeof(DBTestList)).Deserialize(reader);
Assert.IsTrue(db.TestList2[0].TestStr == "test21");
}
}
I read a lot of posts on this subject but none helped.
Do you have an idea ?
Thanks,
Best regards.
EDIT :
To give a more detailed idea of the classes used in the lists, here's one basic implementation.
All the types are derived from the parent one a_SolidElement, adding only a few properties (basic value types and/or enum) :
public abstract class a_SolidElement
{
[XmlIgnore]
public int Position { get; set; }
public virtual double Thickness { get; set; }
public virtual double Density { get; set; }
public string SupplierName { get; set; }
public string Name { get; set; }
}
public enum ElementType
{
Undefined=0,
TypeA,
TypeB
}
public class FooType:a_SolidElement
{
public double AdditionalData { get; set; }
public e_ElementType ElementType { get; set; }
}
dbc was actually right about the bad IXmlSerializable implementation in his comment :
In one of my classes, I had one property of a type I didn't write, with a problem in the readXml method.
I didn't see it at first because I successively removed some properties to see which one caused the problem but this one was still in the first deserialized item so that even if the subsequent ones didn't have it, the reader was still already messed up by the first one.
It's so obvious now that I feel bad for asking the question in the first place !
Thank you very much for the help !
Related
I store the items displayed on my homepage as an XML string (in the settings).
<?xml version=""1.0""?>
<HomePageItemList>
<PlantHomePageItem>
<Name>Plant1</Name>
</PlantHomePageItem>
<PlantHomePageItem>
<Name>Plant2</Name>
</PlantHomePageItem>
<AdminHomePageItem>
<Name>Admin1</Name>
</AdminHomePageItem>
</HomePageItemList>
Some items represent a plant PlantHomePageItem, others an admin tool AdminHomePageItem. Both inherit the same base class HomePageItem
[XmlRoot("HomePageItemList")]
public class TestSerialization
{
[XmlArray("HomePageItemList")]
[XmlArrayItem("PlantHomePageItem", Type = typeof(PlantHomePageItem))]
[XmlArrayItem("AdminHomePageItem", Type = typeof(AdminHomePageItem))]
public List<HomePageItem> HomePageItemList { get; set; }
}
[XmlInclude(typeof(PlantHomePageItem))]
[XmlInclude(typeof(AdminHomePageItem))]
public class HomePageItem
{
[XmlElement("Name")]
public string Name { get; set; }
}
public class PlantHomePageItem : HomePageItem { }
public class AdminHomePageItem : HomePageItem { }
When I try an deserialize it, I have no runtime error, just an empty object...
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
XmlSerializer xs = new XmlSerializer(typeof(TestSerialization));
TestSerialization obj = (TestSerialization)xs.Deserialize(ms);
Console.WriteLine(obj.HomePageItemList.Count);
foreach (var item in obj.HomePageItemList)
{
Console.WriteLine("{0}: {1}", item.GetType().Name, item.Name);
}
returns
0
A .NET Fiddle with the entire thing is available: https://dotnetfiddle.net/f51b0K
Change your TestSerialization class to this...
[XmlRoot("HomePageItemList")]
public class TestSerialization
{
[XmlElement("PlantHomePageItem", Type = typeof(PlantHomePageItem))]
[XmlElement("AdminHomePageItem", Type = typeof(AdminHomePageItem))]
public List<HomePageItem> HomePageItemList { get; set; }
}
Your modified example...
This is my first question on SO, please let me know if I am doing anything wrong!
I am trying to parse an XML similar to this:
<LiveUpdate>
<CityID>F0A21EA2</CityID>
<CityName>CityTown</CityName>
<UserName>john</UserName>
<ApplicationDetails>
<ApplicationDetail
Application="AC"
Licensed="true"
Version="2015.2"
Patch="0001"
/>
<ApplicationDetail
Application="AP"
Licensed="true"
Version="2015.2"
Patch="0002"
/>
</ApplicationDetails>
</LiveUpdate>
I have classes that look like this:
public class Client
{
public string cityID { get; set; }
public string cityName { get; set; }
public string userName { get; set; }
public List<Apps> appList { get; set; }
}
public class Apps
{
public string app { get; set; }
public string licensed { get; set; }
public string version { get; set; }
public string patch { get; set; }
}
I need to be able to have a client class with a list of all the application details to be iterated over.
So far the best I've come up with is:
XDocument xml = XDocument.Load(#"C:\blah\Desktop\1.xml");
var liveUpdate = xml.Root;
var clients = (from e in liveUpdate.Elements()
select new Client()
{
cityID = e.Element("CityID").Value,
cityName = e.Element("CityName").Value,
userName = e.Element("UserName").Value,
appList = e.Elements("ApplicationDetails")
.Select(a => new Apps()
{
app = a.Element("Application").Value,
licensed = a.Element("Licensed").Value,
version = a.Element("Version").Value,
patch = a.Element("Patch").Value
}).ToList()
});
However, I'm currently running into an error that says Object reference not set to an instance of an object.
I've seen some similar examples on here, but not that deal with data before the multiple children.
I'm fairly new to XML and Linq so any help here would be greatly appreciated!
Your XML only contains one LiveUpdate tag, so rather than iterating over all of the elements inside of it, you just want to look at the Root element.
In ApplicationDetails, Application, Licensed and such are attributes, not elements. Use .Attribute() to access them.
ApplicationDetails is a single tag, and inside it you have ApplicationDetail tags.
There is no DateTime element in your LiveUpdate tag.
This works:
var liveUpdate = xml.Root;
var e = liveUpdate;
var clients = new Client()
{
cityID = e.Element("CityID").Value,
cityName = e.Element("CityName").Value,
userName = e.Element("UserName").Value,
//dateTime = e.Element("DateTime").Value,
appList = e.Element("ApplicationDetails").Elements("ApplicationDetail")
.Select(a => new Apps()
{
app = a.Attribute("Application").Value,
licensed = a.Attribute("Licensed").Value,
version = a.Attribute("Version").Value,
patch = a.Attribute("Patch").Value
}).ToList()
};
Since you have already defined a class into which you wish to deserialize, you can use XmlSerializer to deserialize it for you.
First, let's rename some of your property names to more closely match the XML and c# naming conventions:
[XmlRoot("LiveUpdate")]
public class Client
{
public string CityID { get; set; }
public string CityName { get; set; }
public string UserName { get; set; }
[XmlArray("ApplicationDetails")]
[XmlArrayItem("ApplicationDetail")]
public List<Apps> AppList { get; set; }
}
public class Apps
{
[XmlAttribute]
public string Application { get; set; }
[XmlAttribute]
public bool Licensed { get; set; }
[XmlAttribute]
public string Version { get; set; }
[XmlAttribute]
public string Patch { get; set; }
}
Then add the following extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString)
{
using (StringReader reader = new StringReader(xmlString))
{
object result = new XmlSerializer(typeof(T)).Deserialize(reader);
if (result is T)
{
return (T)result;
}
}
return default(T);
}
public static T LoadFromFile<T>(string filename)
{
using (var fs = new FileStream(filename, FileMode.Open))
{
object result = new XmlSerializer(typeof(T)).Deserialize(fs);
if (result is T)
{
return (T)result;
}
}
return default(T);
}
}
Now you can deserialize from your XML file as follows:
string fileName = #"C:\blah\Desktop\1.xml";
var client = XmlSerializationHelper.LoadFromFile<Client>(fileName);
I manually updated your Client class to map correctly to the provided XML, but if you wanted to do it automatically, see here: Generate C# class from XML.
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");
}
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);
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);
}
}
...