Click here to Skip to main content
14,971,742 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
have been over numerous serialization articles trying to figure this one out but getting no where. The code below simple but it is a bit longer than I would have liked to have posted but have tried to keep it to a minimum while still explaining my issue. Simply : I want to serialize a class that contains a list of another class but I only want to serialize a public ID property for all objects within the list. What makes it complicated is that the class that is in the list needs to be able to be fully serialized itself separately (i.e. in a separate XML file) which means I cannot simply tag all the non ID properties with XmlIgnore attribute.

What I have tried:

I have tried using the OnSerializing and on DeSerialized attributes but for whatever reason they never seem to fire. In the code below, the class Material is stored in it's own separate XML document that is read in to an application on startup and made globally available through the application. There is a separate XML document containing serialized Item class. When deserializing these objects I only want to deserialize the Material.ID property and then find the matching object within my globally available list.

I dont really care at the moment if this is XML,or JSON at the moment (XML just seemed easier). I will eventually need JSON but right now just need to get something going for testing and prototyping before moving to a more robust serializer.


C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Windows.Forms;
using System.Xml.Serialization;

namespace SerializationTestNET
{
  static class Program
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);

      GlobalData.InitDefaultData();

      Category PlasticItems = new Category() { ID = "ALLPLAS", Name = "All Plastic Items" };
      Category OtherItems = new Category() { ID = "NONPLAS", Name = "Non Plastic" };

      Item PlasticAndSteel = new Item() { ID = "PLASSTEL", Name = "Plastic and Steel", Category = PlasticItems };
      PlasticAndSteel.Materials.Add(GlobalData.Plastic);
      PlasticAndSteel.Materials.Add(GlobalData.Steel);

      Item PlasticAndRubber = new Item() { ID = "PLASRUBR", Name = "Plastic and Rubber", Category = PlasticItems };
      PlasticAndRubber.Materials.Add(GlobalData.Plastic);
      PlasticAndRubber.Materials.Add(GlobalData.Rubber);

      Item Steel = new Item() { ID = "STEL", Name = "Steel", Category = OtherItems };
      Steel.Materials.Add(GlobalData.Steel);

      Item Rubber = new Item() { ID = "RUBR", Name = "Rubber", Category = OtherItems };
      Rubber.Materials.Add(GlobalData.Rubber);

      PlasticItems.Items.Add(PlasticAndSteel);
      PlasticItems.Items.Add(PlasticAndRubber);
      OtherItems.Items.Add(Steel);
      OtherItems.Items.Add(Rubber);

      MaterialDocument materialDocument = new MaterialDocument() { Materials = GlobalData.AllMaterials };

      using (StreamWriter writer = new StreamWriter("C:\\Temp\\Materials.xml"))
        materialDocument.Serialize(writer);

      MaterialDocument materialsDeSerializedDocument;
      using (StreamReader reader = new StreamReader("C:\\Temp\\Materials.xml"))
        materialsDeSerializedDocument = MaterialDocument.DeSerialize(reader);

      CategoryDocument categoriesDocument = new CategoryDocument();
      categoriesDocument.Categories.Add(PlasticItems);
      categoriesDocument.Categories.Add(OtherItems);

      using (StreamWriter writer = new StreamWriter("C:\\Temp\\Categories.xml"))
        categoriesDocument.Serialize(writer);

      CategoryDocument categoriesDocumentDeSerializeDocument;
      using (StreamReader reader = new StreamReader("C:\\Temp\\Categories.xml"))
        categoriesDocumentDeSerializeDocument = CategoryDocument.DeSerialize(reader);

      Application.Run(new Form1());
    }
  }

  public static class GlobalData
  {
    public static List<Material> AllMaterials { get; set; } = new List<Material>(); // Globally available data that contains both static default data as well as user data added in at a later stage
    [XmlIgnore]
    public static List<Item> AvailableItems { get; set; } = new List<Item>();  // A list of all available items loaded in the currnet running application instance
    [XmlIgnore]
    public static Material Plastic { get; set; }
    public static Material Steel { get; set; }
    public static Material Rubber { get; set; } // Static default material data that is referenced through the application

    public static Material GetMaterial(string id) // Locate and return a material by ID
    {
      foreach (Material material in AllMaterials)
        if (material.ID == id)
          return material;

      return null;
    }

    public static void InitDefaultData()
    {
      AllMaterials.Add(Plastic = new Material() { ID = "PLAS", Name = "Plastic", MaterialValue = 5 });
      AllMaterials.Add(Steel = new Material() { ID = "STEL", Name = "Steel", MaterialValue = 5 });
      AllMaterials.Add(Rubber = new Material() { ID = "RUBR", Name = "Rubber", MaterialValue = 7 });
    }

  }

  public class BaseClass // Base class for all application objects requiring an ID and Name property
  {
    public string ID { get; set; }
    public string Name { get; set; }
  }

  public class Material : BaseClass
  {
    public decimal MaterialValue;
  }
  public class Item : BaseClass
  {
    public List<Material> Materials { get; set; } = new List<Material>();

    [XmlIgnore]
    public Category Category { get; set; }

    public List<string> MaterialIDs { get; set; } = new List<String>();

    public string CategoryID { get; set; }

    [OnSerializing]
    public void CreateIDList()
    {
      MaterialIDs.Clear(); // Clear the list and recreate it tomake it current with all Materials in the list.
      foreach (Material material in Materials)
        MaterialIDs.Add(material.ID);

      CategoryID = Category.ID; // Make sure we have the current Category ID to serialize
    }

    [OnDeserialized]
    public void EstablishLinks()
    {
      foreach (string id in MaterialIDs) // Loop through all ids that were derserialized and link them back to their correct material objects.
        Materials.Add(GlobalData.GetMaterial(id));
    }
  }

  public class Category : BaseClass
  {
    public List<Item> Items { get; set; } = new List<Item>();
    public string CategoryNotes { get; set; }
  }

  public class CategoryDocument // Class storing lists of categories for use within the application and for serialization and deserilation of this data.
  {
    public List<Category> Categories { get; set; } = new List<Category>();

    public string Serialize(StreamWriter writer)
    {
      var serializer = new XmlSerializer((typeof(CategoryDocument)));
      serializer.Serialize(writer, this);
      return writer.ToString();
    }

    public static CategoryDocument DeSerialize(StreamReader reader)
    {
      var serializer = new XmlSerializer(typeof(CategoryDocument));
      return serializer.Deserialize(reader) as CategoryDocument;
    }
  }

  public class MaterialDocument // Class storing lists of material items
  {
    public List<Material> Materials { get; set; } = new List<Material>();

    public string Serialize(StreamWriter writer)
    {
      var serializer = new XmlSerializer((typeof(MaterialDocument)));
      serializer.Serialize(writer, this);
      return writer.ToString();
    }

    public static MaterialDocument DeSerialize(StreamReader reader)
    {
      var serializer = new XmlSerializer(typeof(MaterialDocument));
      return serializer.Deserialize(reader) as MaterialDocument;
    }
  }
}


Current Categories XML file :

XML
<?xml version="1.0" encoding="utf-8"?>
<CategoryDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Categories>
    <ID>ALLPLAS</ID>
    <Name>All Plastic Items</Name>
    <Items>
      <Item>
        <ID>PLASSTEL</ID>
        <Name>Plastic and Steel</Name>
        <Materials>
          <Material>
            <ID>PLAS</ID>
            <Name>Plastic</Name>
            <MaterialValue>5</MaterialValue>
          </Material>
          <Material>
            <ID>STEL</ID>
            <Name>Steel</Name>
            <MaterialValue>5</MaterialValue>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
      <Item>
        <ID>PLASRUBR</ID>
        <Name>Plastic and Rubber</Name>
        <Materials>
          <Material>
            <ID>PLAS</ID>
            <Name>Plastic</Name>
            <MaterialValue>5</MaterialValue>
          </Material>
          <Material>
            <ID>RUBR</ID>
            <Name>Rubber</Name>
            <MaterialValue>7</MaterialValue>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
    </Items>
  </Categories>
  <Categories>
    <ID>NONPLAS</ID>
    <Name>Non Plastic</Name>
    <Items>
      <Item>
        <ID>STEL</ID>
        <Name>Steel</Name>
        <Materials>
          <Material>
            <ID>STEL</ID>
            <Name>Steel</Name>
            <MaterialValue>5</MaterialValue>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
      <Item>
        <ID>RUBR</ID>
        <Name>Rubber</Name>
        <Materials>
          <Material>
            <ID>RUBR</ID>
            <Name>Rubber</Name>
            <MaterialValue>7</MaterialValue>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
    </Items>
  </Categories>
</CategoryDocument>



Desired Categories XML file :

XML
<?xml version="1.0" encoding="utf-8"?>
<CategoryDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Categories>
    <ID>ALLPLAS</ID>
    <Name>All Plastic Items</Name>
    <Items>
      <Item>
        <ID>PLASSTEL</ID>
        <Name>Plastic and Steel</Name>
        <Materials>
          <Material>
            <ID>PLAS</ID>
          </Material>
          <Material>
            <ID>STEL</ID>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
      <Item>
        <ID>PLASRUBR</ID>
        <Name>Plastic and Rubber</Name>
        <Materials>
          <Material>
            <ID>PLAS</ID>
          </Material>
          <Material>
            <ID>RUBR</ID>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
    </Items>
  </Categories>
  <Categories>
    <ID>NONPLAS</ID>
    <Name>Non Plastic</Name>
    <Items>
      <Item>
        <ID>STEL</ID>
        <Name>Steel</Name>
        <Materials>
          <Material>
            <ID>STEL</ID>
          </Material>
        </Materials>
        <MaterialIDs />
      </Item>
      <Item>
        <ID>RUBR</ID>
        <Name>Rubber</Name>
        <Materials>
          <Material>
            <ID>RUBR</ID>
          </Material>
        </Materials>
      </Item>
    </Items>
  </Categories>
</CategoryDocument>


Materials XML output file :

<?xml version="1.0" encoding="utf-8"?>
<MaterialDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Materials>
    <Material>
      <ID>PLAS</ID>
      <Name>Plastic</Name>
      <MaterialValue>5</MaterialValue>
    </Material>
    <Material>
      <ID>STEL</ID>
      <Name>Steel</Name>
      <MaterialValue>5</MaterialValue>
    </Material>
    <Material>
      <ID>RUBR</ID>
      <Name>Rubber</Name>
      <MaterialValue>7</MaterialValue>
    </Material>
  </Materials>
</MaterialDocument>
Posted
Updated 6-Aug-17 20:31pm
v2
Comments
BillWoodruff 7-Aug-17 0:14am
   
+5 for an interesting and carefully written question.
BillWoodruff 8-Aug-17 21:41pm
   
I am curious to ask you what the reason for "double" serialization is here.

Also: why have you chosen JSON format ?
BillWoodruff 10-Aug-17 22:18pm
   
Member 13348717 wrote (in comment below) : "The issue is that the ID properties have to be public to be serialized"

This is not true for WCF/DataContract:

https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/using-data-contracts

"You can apply the DataMemberAttribute attribute to fields, and properties.

Member accessibility levels (internal, private, protected, or public) do not affect the data contract in any way."

However, if the keys must be secured, then, imho, you should plan for using encryption.

This question required a little research, however, the solution is a simple one - Use an XSLT to transform the full XML data into an abbreviated or a subset XML dataset. Here are the 3 links that helped with the solution:

1. c# - How to transform XML as a string w/o using files in .NET? - Stack Overflow[^]
2. Converting XML file to another XML file using XSLT - Stack Overflow[^]
3. XSLT Transformation[^]

The following XML Converter Helper class was used:
C#
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace XmlJsonSubsetProperties
{
    public static class XmlConverter
    {
        public static string FromClass<T>(T data, XmlSerializerNamespaces ns = null)
        {
            string response = string.Empty;

            var ms = new MemoryStream();
            try
            {
                ms = FromClassToStream(data, ns);

                if (ms != null)
                {
                    ms.Position = 0;
                    using (var sr = new StreamReader(ms))
                        response = sr.ReadToEnd();
                }
            }
            finally
            {
                // don't want memory leaks...
                ms.Flush();
                ms.Dispose();
                ms = null;
            }

            return response;
        }

        public static MemoryStream FromClassToStream<T>(T data, XmlSerializerNamespaces ns = null)
        {
            var stream = default(MemoryStream);

            if (data != null)
            {
                var settings = new XmlWriterSettings()
                {
                    Encoding = Encoding.UTF8,
                    Indent = true,
                    ConformanceLevel = ConformanceLevel.Auto,
                    CheckCharacters = true,
                    OmitXmlDeclaration = false
                };

                try
                {
                    //XmlSerializer ser = new XmlSerializer(typeof(T));
                    XmlSerializer serializer = XmlSerializerFactoryNoThrow.Create(typeof(T));

                    stream = new MemoryStream();
                    using (XmlWriter writer = XmlWriter.Create(stream, settings))
                    {
                        serializer.Serialize(writer, data, ns);
                        writer.Flush();
                    }
                    stream.Position = 0;
                }
                catch (Exception ex)
                {
                    stream = default(MemoryStream);
#if DEBUG
                    Debug.WriteLine(ex);
                    Debugger.Break();
#endif
                }
            }
            return stream;
        }

        public static T ToClass<T>(string data)
        {
            var response = default(T);

            if (!string.IsNullOrEmpty(data))
            {
                var settings = new XmlReaderSettings() { IgnoreWhitespace = true };

                try
                {
                    XmlSerializer serializer = XmlSerializerFactoryNoThrow.Create(typeof(T));
                    XmlReader reader = XmlReader.Create(new StringReader(data), settings);
                    response = (T)Convert.ChangeType(serializer.Deserialize(reader), typeof(T));
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    Debugger.Break();
                }
            }
            return response;
        }
    }
}

Along with this c# - XmlSerializer giving FileNotFoundException at constructor fix[^]:
C#
using System;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace XmlJsonSubsetProperties
{
    public static class XmlSerializerFactoryNoThrow
    {
        public static Dictionary<Type, XmlSerializer> cache = new Dictionary<Type, XmlSerializer>();

        private static readonly object SyncRootCache = new object();

        public static XmlSerializer Create(Type type)
        {
            XmlSerializer serializer;

            lock (SyncRootCache)
                if (cache.TryGetValue(type, out serializer))
                    return serializer;

            //multiple variables of type of one type are the same instance
            lock (type)
                //constructor XmlSerializer.FromTypes does not throw the first chance exception           
                serializer = XmlSerializer.FromTypes(new[] { type })[0];

            lock (SyncRootCache) cache[type] = serializer;
            return serializer;
        }
    }
}

And lastly, the test solution:
C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
using System.Xml.Xsl;

namespace XmlJsonSubsetProperties
{
    internal static class Program
    {
        private static void Main()
        {
            var plasticItems = new Category() { ID = "ALLPLAS", Name = "All Plastic Items" };
            var otherItems = new Category() { ID = "NONPLAS", Name = "Non Plastic" };

            var fullList = new CategoryList()
            {
                Categories = new List<Category>()
                {
                    plasticItems,
                    otherItems
                }
            };

            // input-xml
            string xmlinput = XmlConverter.FromClass(fullList);

            // xslt
            var lines = xmlinput.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            string header = lines[1];
            string footer = lines[lines.Length - 1];
            const string idFieldName = nameof(Category.ID);
            const string catFieldName = nameof(CategoryList.Categories);
            string xsltinput = $"<?xml version=\"1.0\" encoding=\"iso-8859-1\"?><xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:template match=\"/\">{header}<xsl:for-each select=\"{nameof(CategoryList)}/{catFieldName}\"><{catFieldName}><{idFieldName}><xsl:value-of select=\"{idFieldName}\"/></{idFieldName}></{catFieldName}></xsl:for-each>{footer}</xsl:template></xsl:stylesheet>";

            // output-xml
            string xmloutput = Transform(xmlinput, xsltinput);

            var xmlIdList = XmlConverter.ToClass<CategoryList>(xmloutput);
            Debugger.Break();
        }

        private static string Transform(string xmlinput, string xsltinput)
        {
            string xmloutput;
            // Prepare input-xml
            var doc = new XPathDocument(new StringReader(xmlinput));

            // Prepare XSLT
            var xslt = new XslTransform();
            // Creates a XmlReader from your xsl string
            using (XmlReader xmlreader = XmlReader.Create(new StringReader(xsltinput)))
            {
                //Load the stylesheet.
                xslt.Load(xmlreader);

                // transform
                using (var sw = new StringWriter())
                {
                    xslt.Transform(doc, null, sw);

                    // save to string
                    xmloutput = sw.ToString();
                }
            }

            return xmloutput;
        }
    }

    public class Category
    {
        public string ID { get; set; }
        public string Name { get; set; }
    }

    [XmlRoot(nameof(CategoryList))]
    public class CategoryList
    {
        [XmlElement]
        public List<Category> Categories { get; set; }
    }
}

This is the Full XML dataset:
XML
<?xml version="1.0" encoding="utf-8"?>
<CategoryList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Categories>
    <ID>ALLPLAS</ID>
    <Name>All Plastic Items</Name>
  </Categories>
  <Categories>
    <ID>NONPLAS</ID>
    <Name>Non Plastic</Name>
  </Categories>
</CategoryList>

The XSLT transformation:
XML
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:for-each select="CategoryList/Categories">
    <Categories>
	  <ID><xsl:value-of select="ID"/></ID>
    </Categories>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

And the subset output:
XML
<?xml version="1.0" encoding="utf-16"?>
<CategoryList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Categories>
    <ID>ALLPLAS</ID>
  </Categories>
  <Categories>
    <ID>NONPLAS</ID>
  </Categories>
</CategoryList>


UPDATE: The following XSLT will transform your original CategoryDocument to the "ID" version as per your updated question:
XML
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
  <CategoryDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsl:for-each select="CategoryDocument/Categories">
    <Categories>
      <ID><xsl:value-of select="ID"/></ID>
      <Name><xsl:value-of select="Name"/></Name>
        <Items>
          <xsl:for-each select="Items/Item">
          <Item>
            <ID><xsl:value-of select="ID"/></ID>
            <Name><xsl:value-of select="Name"/></Name>
            <Materials>
              <xsl:for-each select="Materials/Material">
              <Material>
                <ID><xsl:value-of select="ID"/></ID>
              </Material>
              </xsl:for-each>
            </Materials>
          </Item>
          </xsl:for-each>
        </Items>
    </Categories>
    </xsl:for-each>
	</CategoryDocument>
  </xsl:template>
</xsl:stylesheet>

NOTE: This is an updated XSLT only. You will need to update the above code with your classes and this XSLT.

Enjoy! :)
   
v4
Comments
BillWoodruff 7-Aug-17 0:12am
   
+5 good stuff !
Graeme_Grant 7-Aug-17 0:16am
   
Thanks. I enjoy these types of questions/problems... the hard part is finding the simple solution...

I know that you have asked me to write an article on JSON, and that's underway. I might follow it up with an XML one... ;)
BillWoodruff 7-Aug-17 2:38am
   
I also enjoy these types of problems; I learn something from going over what's possible.

Ever since I discovered WCF's DataContract facilities (see code example below), I've never "looked back" at JSON, but, I do understand it has a role to play. Using GZip (see below) you get incredible compression of the insanely verbose XML :)
Graeme_Grant 7-Aug-17 3:09am
   
That is an interesting way of doing it. :)

I still prefer JSON over XML... :P
BillWoodruff 8-Aug-17 8:00am
   
Graeme, be sure and check out Mehdi Gholam's FastJSON work with serialization here on CP.

https://www.codeproject.com/Articles/345070/fastBinaryJSON

https://www.codeproject.com/Articles/345070/fastBinaryJSON

cheers, Bill
Graeme_Grant 8-Aug-17 8:16am
   
Thanks... I have but still prefer Newtownsoft ;)
BillWoodruff 8-Aug-17 8:55am
   
Be assured, I have no desire to "convert" anyone from what they prefer to ... something else :)
Graeme_Grant 8-Aug-17 9:05am
   
I understood where you were coming from. :)

Once I finish and publish the article, it should be apparent why.
BillWoodruff 8-Aug-17 21:35pm
   
Are you aware that WCF provides JSON facilities via 'DataContractJsonSerializer ?
Graeme_Grant 9-Aug-17 2:00am
   
Yes, I am actually. I used to use them but found them too limiting. ;)
Member 13348717 7-Aug-17 2:33am
   
Thanks Graeme for the detailed response and the references. I am picking through it now to try to adapt it to meet requirements.

The issue I have is with the Material member property object, bnot the root Categoty class. When the Category objects get serialized, the Categories[x].Items[y].Materials list members that get serialized are the ones that I need just the ID, not the full Material class object.

I have edited the original question to show the XML I was getting with the original code and also what I am trying to achieve. I am trying to adapt your solution from working on a root object ( Category ) the a member property within a list item ( Category[x].Items[y].Material ) but not entirely sure how to make this work on a member rather than the root class.
Graeme_Grant 7-Aug-17 2:37am
   
Sure, the principle is the same, the XSLT will describe how to output the data. I only went one layer deep.

All that you need to do is add your class structure, mock data, update the XSLT and run. :)
Graeme_Grant 7-Aug-17 3:08am
   
I've done the XSLT mapping for you... pretty simple. ;)

You will need to update the solution with your classes and the XSLT.
Member 13348717 8-Aug-17 22:16pm
   
Has been interesting following this conversation. My initial requirement was just serialization and I chose XML because it seemed easiest but I will also have a need for JSON ( I think as the project will move forward with a web data requirement as well ). All this of course means I need to do a lot more reading
Graeme_Grant 8-Aug-17 22:44pm
   
You are welcome. I'll add your scenario to the article that I am writing on working with JSON data. I'll post a note here once it is published. :)
The WCF Serialization facilities make this type of task relatively easy, imho: you can serialize all of a Class, or part of it ... the use of Attributes controls what is serialized.

In this example serialization occurs twice: first, to save an instance of 'Class1 without its list of instances of 'Class2, but, including its list of 'Class2 instance Guids; and, second, to save the list of instances of 'Class2 in 'Class1.

First, here's what the usage of the code could look like:
private void button2_Click(object sender, EventArgs e)
{
    // make a 'Class1 instance
    Class1  class1 = new Class1();

    // add some 'Class2 instances
    class1.GetNewClass2("c2a");
    class1.GetNewClass2("c2b");
    class1.GetNewClass2("c2c");

    // serialize the 'Class1 instance
    SerializationManager.Serialize<Class1>(class1, "test1", false);

    // deserialize the 'Class1 instance from file
    Class1 c1resurrected = SerializationManager.DeSerialize<Class1>("test1", false);

    // serialize the list of 'Class2 instances in 'Class1
    SerializationManager.Serialize<List<Class2>>(class1.Class2s, "test2", false);

    // deserialize the list of 'Class2 instances from file
    List<Class2> c2Listresurrected = SerializationManager.DeSerialize<List<Class2>>("test2", false);

    // set the list of 'Class2 instances in the restored
    // 'Class1 instance to the resored list of 'Class2 instances
    c1resurrected.Class2s = c2Listresurrected;
}


using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;

namespace DataContractDemo
{
    [DataContract]
    public class Class1
    {
        [DataMember]
        public List<Guid> Class2IDs { get; set; }

        [NonSerialized] public List<Class2> Class2s;

        [NonSerialized] private static int c2Counter = 0;

        public Class1()
        {
            Class2IDs = new List<Guid>();
            Class2s = new List<Class2>();
        }

        public Class2 GetNewClass2(string name)
        {
            var newClass2 = new Class2(Guid.NewGuid(), $"C2_{c2Counter++}");

            Class2IDs.Add(newClass2.ID);

            Class2s.Add(newClass2);

            return newClass2;
        }
    }

    [DataContract]
    public class Class2
    {
        [DataMember]
        public Guid ID { get; set; }

        [DataMember]
        public string Name { get; set; }

        public Class2(Guid id, string name)
        {
            ID = id;
            Name = name;
        }
    }

    public static class SerializationManager
    {
        private static string baseFolderPath = @"C:\Users\YourUserName\Desktop\TestDCSerialization\";

        private static DataContractSerializer dcs;

        public static void Serialize<T>(T instance, string filename1 = "", bool usegzip = true)
        {
            dcs = new DataContractSerializer(typeof(T));

            filename1 = baseFolderPath + filename1 + (usegzip ? @".gz" : @".xml");

            if (File.Exists(filename1)) File.Delete(filename1);

            // gzip writer
            if (usegzip)
            {
                using (var compressedFileStream = File.Create(filename1))
                {
                    using (var compressionStream =
                        new GZipStream(compressedFileStream, CompressionLevel.Optimal, true))
                    {
                        dcs.WriteObject(compressionStream, instance);
                        compressionStream.Close();
                    }
                }
            }
            else
            {
                using (var writer = new FileStream(filename1, FileMode.OpenOrCreate, FileAccess.Write))
                {
                    dcs.WriteObject(writer, instance);
                }
            }
        }

        public static T DeSerialize<T>(string filename1 = "", bool usegzip = true)
        {
            dcs = new DataContractSerializer(typeof(T));

            filename1 = baseFolderPath + filename1 + (usegzip ? @".gz" : @".xml");

            if (!File.Exists(filename1))
            {
                throw new FileNotFoundException($"{filename1} does not exist");
            }

            T result;

            if (usegzip)
            {
                using (var xmlStream = new MemoryStream())
                {
                    using (var fs = File.Open(filename1, FileMode.Open))
                    {
                        using (var deCompStream =
                            new GZipStream(fs, CompressionMode.Decompress, true))
                        {
                            deCompStream.CopyTo(xmlStream);
                            deCompStream.Close(); // using 'Close here fixes a common problem with WCF
                            // must reset to beginning of stream
                            xmlStream.Position = 0;
                        }

                        result = (T) dcs.ReadObject(xmlStream);

                        xmlStream.Close();
                    }
                }
            }
            else
            {
                var reader = new FileStream(filename1, FileMode.Open, FileAccess.Read);
                result = (T) dcs.ReadObject(reader);
            }

            return result;
        }
    }
}
   
v2
Comments
Member 13348717 7-Aug-17 3:33am
   
Thanks Bill,

Unless I am missing something, this approach then introduces a problem with keeping the ID and object list in sync

Both lists must be exposed publicly to allow access to the member lists. the List<class2> property for access to the actual data and List<class2id> property so that the ID lists gets serialized. Although there may be a separate public method that add new or existing Class2 objects as list members under Class1, there is nothing to prevent developers (either purposely or mistakenly) from adding items to 1 of these lists independently which will then throw the lists (and subsequent serialization) out of sync. I tried adding code originally to sync the ID list with the object list and tag it with the [OnSerialized] attribute but it never fired. I could also place code within the Serialize method to sync these properties but in the real world application (not just the example above) this involves numerous nested iteration loops to achieve this syncing.

Of course nested iterations must then be performed to resync the object lists with the deserialized ID lists once the XML has been deserialized.
BillWoodruff 7-Aug-17 6:30am
   
I believe (because I've implemented such code in the past) that the points you raise ... about controlling access ... are easily handled by using the appropriate accessor declarations on the various class' objects, and/or using nested/or/sealed Classes, etc.

It's your intention (I think) to separate out the Instances from their Id's. If you want no access to the Class2 instances, declare Class2 as 'internal, nest it in Class1. Then handle all interaction via public Methods in Class1.

cheers, Bill
Member 13348717 8-Aug-17 22:15pm
   
I need access to the class 2 instances. I really only need access to the ID members for serialization purposes. The issue is that the ID properties have t be public to be serialized meaning I have to have both the instance and the separate property declared as public.
Member 13348717 8-Aug-17 22:19pm
   
I accepted the first XSLT solution as I liked the idea of being able to keep the translation separate to the code. Don't know why but it seemed a more elegant solution to me rather than having to expose more properties than I really needed to. Now comes the hard part of actually implementing it and adapting is across my range of classes :)
BillWoodruff 9-Aug-17 8:59am
   
I'm glad you got some useful ideas here !

cheers, Bill

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900