Link to home
Start Free TrialLog in
Avatar of trevor1940
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


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;
            }
        }
}

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Again, extension methods work on existing INSTANCES of classes
Technically, you can invoke an extension method against a null reference since extension methods are just compiler tricks to create static methods. Doing so, however, could be confusing to other developers.
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.
Avatar of trevor1940
trevor1940

ASKER

I think I follow what you said however

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);
        }

    }

Open in new window



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);
        }

    }

Open in new window




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);
            }
        }
}

Open in new window

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);
        }
    }

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi

I entered this and let .NET offer suggestions

  var ExistingDownloadLog = Utility.FromXml<List<DownloadLog>>("history", @"C:\temp\EE_History\DownloadLog.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
// 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);
        }
    }

Open in new window

Any chance of help with this error?

Error	CS1503	Argument 1: cannot convert from 'object' to 'DownloadLog'

Open in new window

Change your return type to "T" instead of "object" in your "FromXml" method.
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);

Open in new window

You're casting there, however, I'm talking about here:

internal static object FromXml<T>(string rootName, string xmlFile)

Open in new window

You need to cast it on the receiving side:
var ExistingDownloadLog = (List<DownloadLog>)Utility.FromXml<List<DownloadLog>>("history", @"K:\temp\EE_History\DownloadLog.xml");

Open in new window


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.
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


// 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);
        }
    }

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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

                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);


                }

Open in new window


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

Open in new window


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:
        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);
        }

Open in new window


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();

Open in new window


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.
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