trevor1940
asked on
c#: xmlserializer deserialize with inheritance
Hi
How do I write the FromXml to read an xml file into the given opbject?
Once read in I need to test if a given OldPath exists
How do I write the FromXml to read an xml file into the given opbject?
Once read in I need to test if a given OldPath exists
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
class MainClass
{
class Program
{
// create two logs to hold each list
public static List<HistoryLog> historyLogs = new List<HistoryLog>();
static List<DownloadLog> downloadLogs = new List<DownloadLog>();
public static void Main(string[] args)
{
Console.WriteLine("Hello World");
// Dummy varibles;
int myID = 123;
int FileCount = 234;
string ModName = "Hello World";
string OldDir = @"D:\Path\To\OldPath";
string NewDir = @"D:\Path\To\NewPath";
if(File.Exists("DownloadLog.xml") ){
Console.WriteLine("Hello DownloadLog.xml");
downloadLogs.FromXml("history", "DownloadLog.xml");
foreach (var downloadLog in downloadLogs)
{
Console.WriteLine("download Name: {0} download OldPath: {1} ", downloadLog.Name, downloadLog.OldPath );
}
}
else {
// make a loop just to test
// this is to simulate where the infomation will be
// derived from with some condition deturmining if
// HistoryLog or DownloadLog is populated
for (int i = 1; i < 5; i++)
{
// history log
var historyLog = new HistoryLog()
{
Name = "Hello Word " + i,
Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
Id = myID + i,
Number = myID + i,
ImageCount = FileCount + i,
DownloadedImagesCount = 0,
Finished = "true"
};
historyLogs.Add(historyLog);
// downloadLog
var downloadLog = new DownloadLog()
{
Name = ModName + i,
Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
Id = myID + i,
OldPath = OldDir + i,
NewPath = NewDir + i
};
downloadLogs.Add(downloadLog);
}
// See what there is before Saving
// print 1 element from PostParent and historyLog
foreach (var historyLog in historyLogs)
{
Console.WriteLine("HistoryLog Name: {0} HistoryLog ImageCount: {1} ", historyLog.Name, historyLog.ImageCount);
}
try
{
// Save the XML
historyLogs.ToXml("history", "HistoryLog.xml");
downloadLogs.ToXml("history", "DownloadLog.xml");
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
finally
{
Console.WriteLine("Good bye, crule World!");
}
}
Console.ReadLine();
}// end Main
}
}
// PostParent has common elements
// named PostParent as the xml Node name is post
public class PostParent
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "url")]
public string Url { get; set; }
[XmlElement(ElementName = "id")]
public int Id { get; set; }
}
// has only history Elements Inherets post
// set the node name
[XmlType(TypeName="post")]
public class HistoryLog : PostParent
{
[XmlElement(ElementName = "number")]
public int Number { get; set; }
[XmlElement(ElementName = "imageCount")]
public int ImageCount { get; set; }
[XmlElement(ElementName = "downloadedImagesCount")]
public int DownloadedImagesCount { get; set; }
[XmlElement(ElementName = "finished")]
public string Finished { get; set; }
}
// has only Download Elements Inherets post
// set the node name
[XmlType(TypeName="post")]
public class DownloadLog : PostParent
{
[XmlElement(ElementName = "OldPath")]
public string OldPath { get; set; }
[XmlElement(ElementName = "NewPath")]
public string NewPath { get; set; }
}
// Universal XmlSerializer works on the object takes rootname and File Path
public static class Utility
{
public static void ToXml<T>(this T obj, string rootName, string xmlFile)
{
XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
// can be set if required
var xmlNs = new XmlSerializerNamespaces();
xmlNs.Add(string.Empty, string.Empty);
using (TextWriter writer = new StreamWriter(xmlFile))
{
serializer.Serialize(writer, obj, xmlNs);
}
}
public static FromXml<T>(this T obj, string rootName, string xmlFile)
{
var deserializer = new XmlSerializer(typeof(T));
using (TextReader reader = new StreamReader(xmlFile))
{
return deserializer.Deserialize(reader) as T;
}
}
}
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Yeah, you can do that, but I absolutely agree that it's confusing.
I tried this To/From extension method out years ago on a project when I first learned about them, and then left that project for a while and rotate back on about 2 years later to handle something else. However, back then I didn't know about the null reference because I was copy-and-pasting from another site where they had created an object.
So while I was out, other developers had reused the same methodology for other classes but a few of them had constructors that ran API calls. I was rotated back on to try and figure out some performance issues, one of which was extra API calls going out because they were creating empty objects.
Ultimately, I just find it far cleaner to use plain-old static methods for serializing and deserializing.
I tried this To/From extension method out years ago on a project when I first learned about them, and then left that project for a while and rotate back on about 2 years later to handle something else. However, back then I didn't know about the null reference because I was copy-and-pasting from another site where they had created an object.
So while I was out, other developers had reused the same methodology for other classes but a few of them had constructors that ran API calls. I was rotated back on to try and figure out some performance issues, one of which was extra API calls going out because they were creating empty objects.
Ultimately, I just find it far cleaner to use plain-old static methods for serializing and deserializing.
ASKER
I think I follow what you said however
Changing This
To This caused Errors
Complete Code
Changing This
// Save the XML
HistoryLogs.ToXml("history", "HistoryLog.xml");
public static void ToXml<T>(this T obj, string rootName, string xmlFile)
{
XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
// can be set if required
var xmlNs = new XmlSerializerNamespaces();
xmlNs.Add(string.Empty, string.Empty);
using (TextWriter writer = new StreamWriter(xmlFile))
{
serializer.Serialize(writer, obj, xmlNs);
}
}
To This caused Errors
// Save the XML
HistoryLogs.ToXml<HistoryLog>("history", "HistoryLog.xml");
public static void ToXml<T>(string rootName, string xmlFile)
{
XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
// can be set if required
var xmlNs = new XmlSerializerNamespaces();
xmlNs.Add(string.Empty, string.Empty);
using (TextWriter writer = new StreamWriter(xmlFile))
{
serializer.Serialize(writer, T, xmlNs);
}
}
Complete Code
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
class MainClass
{
class Program
{
// create two logs to hold each list
public static List<HistoryLog> HistoryLogs { get; set; } = new List<HistoryLog>();
public static List<DownloadLog> DownloadLogs { get; set; } = new List<DownloadLog>();
public static void Main(string[] args)
{
Console.WriteLine("Hello World");
// Dummy varibles;
int myID = 123;
int FileCount = 234;
string ModName = "Hello World";
string OldDir = @"D:\Path\To\OldPath";
string NewDir = @"D:\Path\To\NewPath";
if(File.Exists("DownloadLog.xml") ){
Console.WriteLine("Hello DownloadLog.xml");
//downloadLogs.FromXml("history", "DownloadLog.xml");
var ExistingDownloadLog = Utility.FromXml<DownloadLog>("history", "DownloadLog.xml");
DownloadLogs.Add(ExistingDownloadLog);
//Console.WriteLine("DownloadLogs count: {0}", DownloadLog.count());
foreach (var downloadLog in DownloadLogs)
{
Console.WriteLine("download Name: {0} download OldPath: {1} ", downloadLog.Name, downloadLog.OldPath );
}
}
else {
// make a loop just to test
// this is to simulate where the infomation will be
// derived from with some condition deturmining if
// HistoryLog or DownloadLog is populated
for (int i = 1; i < 5; i++)
{
// history log
var historyLog = new HistoryLog()
{
Name = "Hello Word " + i,
Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
Id = myID + i,
Number = myID + i,
ImageCount = FileCount + i,
DownloadedImagesCount = 0,
Finished = "true"
};
HistoryLogs.Add(historyLog);
// downloadLog
var downloadLog = new DownloadLog()
{
Name = ModName + i,
Url = "https://www.example.com/Thread.php?post=post" + i + ".html",
Id = myID + i,
OldPath = OldDir + i,
NewPath = NewDir + i
};
DownloadLogs.Add(downloadLog);
}
// See what there is before Saving
// print 1 element from PostParent and historyLog
foreach (var historyLog in HistoryLogs)
{
Console.WriteLine("HistoryLog Name: {0} HistoryLog ImageCount: {1} ", historyLog.Name, historyLog.ImageCount);
}
try
{
// Save the XML
HistoryLogs.ToXml<HistoryLog>("history", "HistoryLog.xml");
DownloadLogs.ToXml<DownloadLog>("history", "DownloadLog.xml");
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
finally
{
Console.WriteLine("Good bye, crule World!");
}
}
Console.ReadLine();
}// end Main
}
}
// PostParent has common elements
// named PostParent as the xml Node name is post
public class PostParent
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "url")]
public string Url { get; set; }
[XmlElement(ElementName = "id")]
public int Id { get; set; }
}
// has only history Elements Inherets post
// set the node name
[XmlType(TypeName="post")]
public class HistoryLog : PostParent
{
[XmlElement(ElementName = "number")]
public int Number { get; set; }
[XmlElement(ElementName = "imageCount")]
public int ImageCount { get; set; }
[XmlElement(ElementName = "downloadedImagesCount")]
public int DownloadedImagesCount { get; set; }
[XmlElement(ElementName = "finished")]
public string Finished { get; set; }
}
// has only Download Elements Inherets post
// set the node name
[XmlType(TypeName="post")]
public class DownloadLog : PostParent
{
[XmlElement(ElementName = "OldPath")]
public string OldPath { get; set; }
[XmlElement(ElementName = "NewPath")]
public string NewPath { get; set; }
}
// Universal XmlSerializer works on the object takes rootname and File Path
public static class Utility
{
public static void ToXml<T>(string rootName, string xmlFile)
{
XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
// can be set if required
var xmlNs = new XmlSerializerNamespaces();
xmlNs.Add(string.Empty, string.Empty);
using (TextWriter writer = new StreamWriter(xmlFile))
{
serializer.Serialize(writer, T, xmlNs);
}
}
public static T FromXml<T>(string rootName, string xmlFile)
{
var deserializer = new XmlSerializer(typeof(T),new XmlRootAttribute(rootName));
using (TextReader reader = new StreamReader(xmlFile))
{
return (T)deserializer.Deserialize(reader);
}
}
}
ASKER
Also the FromXML method is returning an empty object??
// adjusting path the file exists
var ExistingDownloadLog = Utility.FromXml<DownloadLog>("history", @"C:\temp\EE_History\DownloadLog.xml");
public static T FromXml<T>(string rootName, string xmlFile)
{
var deserializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
using (TextReader reader = new StreamReader(xmlFile))
{
return (T)deserializer.Deserialize(reader);
}
}
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Hi
I entered this and let .NET offer suggestions
var ExistingDownloadLog = Utility.FromXml<List<Downl oadLog>>(" history", @"C:\temp\EE_History\Downl oadLog.xml ");
I got this
internal static object FromXml<T>(string v1, string v2)
I interpreted into this but got errors when trying to add to Global list
I entered this and let .NET offer suggestions
var ExistingDownloadLog = Utility.FromXml<List<Downl
I got this
internal static object FromXml<T>(string v1, string v2)
I interpreted into this but got errors when trying to add to Global list
// Global list
public static List<DownloadLog> DownloadLogs { get; set; } = new List<DownloadLog>();
var ExistingDownloadLog = Utility.FromXml<List<DownloadLog>>("history", @"K:\temp\EE_History\DownloadLog.xml");
// ExistingDownloadLog has 4 items
// Add to Global list so can use it
DownloadLogs.Add(ExistingDownloadLog);
// Error CS1503 Argument 1: cannot convert from 'object' to 'DownloadLog'
internal static object FromXml<T>(string rootName, string xmlFile)
{
var deserializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
using (TextReader reader = new StreamReader(xmlFile))
{
return (T)deserializer.Deserialize(reader);
}
}
ASKER
Any chance of help with this error?
Error CS1503 Argument 1: cannot convert from 'object' to 'DownloadLog'
Change your return type to "T" instead of "object" in your "FromXml" method.
ASKER
Change your return type to "T" instead of "object" in your "FromXml" method
Isn't that what I'm doing here?
return (T)deserializer.Deserialize(reader);
You're casting there, however, I'm talking about here:
internal static object FromXml<T>(string rootName, string xmlFile)
You need to cast it on the receiving side:
NOTE: I just noticed what kaufmed was referring to - it looks like you used to have it correct with T as a return type and it looks like you changed it to an object.
var ExistingDownloadLog = (List<DownloadLog>)Utility.FromXml<List<DownloadLog>>("history", @"K:\temp\EE_History\DownloadLog.xml");
NOTE: I just noticed what kaufmed was referring to - it looks like you used to have it correct with T as a return type and it looks like you changed it to an object.
ASKER
Between you I'm confused
At this point I have this
The Existing XML file is loaded into the new var ExistingDownloadLog but I need it in DownloadLogs so I can use it throughout the code
At this point I have this
The Existing XML file is loaded into the new var ExistingDownloadLog but I need it in DownloadLogs so I can use it throughout the code
// Holding list
public static List<DownloadLog> DownloadLogs { get; set; } = new List<DownloadLog>();
public static void Main(string[] args)
{
..............
var ExistingDownloadLog = (List<DownloadLog>)Utility.FromXml<List<DownloadLog>>("history", @"K:\temp\EE_History\DownloadLog.xml");
// This line errors
// Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.List<DownloadLog>' to 'DownloadLog' SplitXML
//DownloadLogs.Add(ExistingDownloadLog );
foreach (var downloadLog in ExistingDownloadLog)
{
Console.WriteLine("download Name: {0} download OldPath: {1} ", downloadLog.Name, downloadLog.OldPath);
}
.............
public static object FromXml<T>(string rootName, string xmlFile)
{
var deserializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName));
using (TextReader reader = new StreamReader(xmlFile))
{
return (T)deserializer.Deserialize(reader);
}
}
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Hi
Previously I had this
public static T FromXml<T>(string rootName, string xmlFile)
It returned empty hence why I changed it
I'm assuming it was due to not having the calling line correct this is now working with this
from these two I can now see
Are you able to recommend any online tutorials I can read to gain a better understanding?
Previously I had this
public static T FromXml<T>(string rootName, string xmlFile)
It returned empty hence why I changed it
I'm assuming it was due to not having the calling line correct this is now working with this
var ExistingDownloadLog = (List<DownloadLog>)Utility.FromXml<List<DownloadLog>>("history", @"K:\temp\EE_History\DownloadLog.xml");
\\ Not just .Add
DownloadLogs.AddRange(ExistingDownloadLog );
foreach (var downloadLog in ExistingDownloadLog)
{
Console.WriteLine("download Name: {0} download OldPath: {1} ", downloadLog.Name, downloadLog.OldPath);
}
from these two I can now see
(List<DownloadLog>) => static T this is the return type
Utility.FromXml<List<DownloadLog> => FromXml<T> this is the input type
Are you able to recommend any online tutorials I can read to gain a better understanding?
This tutorial is short and covers the basics pretty well:
http://dotnetpattern.com/csharp-generic-methods
I'd also suggest that generic methods can end up having some pretty funky-looking code sometimes. For example:
You can see there's multiple casts (the values of 123 and "abc" have to be cast to an object and then to T), and there's this "default(T)" weirdness. It probably looks pretty weird as-is, but it does make sense once you get more familiar with types and how they work in .NET. I'm not sure where you are in your C# learning, but I'd suggest that generics are probably in the mid-to-advanced tiers of learning. They're not necessarily difficult, but they can unnecessarily confuse things if you're just starting out.
Sometimes when you're starting out, it's just easier to work with the "object" type and use casting when you need to:
It's not efficient to cast things like this, but it can sometimes be a useful crutch while you're learning, and then you can go back and refactor code to be cleaner and more efficient.
http://dotnetpattern.com/csharp-generic-methods
I'd also suggest that generic methods can end up having some pretty funky-looking code sometimes. For example:
public T GetSomeValue<T>()
{
if(typeof(T) == typeof(int))
{
return (T)(object)123;
}
else if (typeof(T) == typeof(string))
{
return (T)(object)"abc";
}
return default(T);
}
You can see there's multiple casts (the values of 123 and "abc" have to be cast to an object and then to T), and there's this "default(T)" weirdness. It probably looks pretty weird as-is, but it does make sense once you get more familiar with types and how they work in .NET. I'm not sure where you are in your C# learning, but I'd suggest that generics are probably in the mid-to-advanced tiers of learning. They're not necessarily difficult, but they can unnecessarily confuse things if you're just starting out.
Sometimes when you're starting out, it's just easier to work with the "object" type and use casting when you need to:
public object GetSomeValue()
{
return 123;
}
int someValue = (int)GetSomeValue();
It's not efficient to cast things like this, but it can sometimes be a useful crutch while you're learning, and then you can go back and refactor code to be cleaner and more efficient.
ASKER
I'm not sure where you are in your C# learning
Nor am I! aspects I find easy, some is just getting used to different syntaxes from other languages, Sometimes I think C# does stuff in a overly complex way, other aspects, like this, has gone 6ft over my head
Thanx for your help