In C# how do I deserialize XML from an older object into the updated object and ignore missing xml elements?

11,301

Solution 1

It should deserialize just fine, it will just use the default constructor to initialize the items. So they will remain unchanged.

Solution 2

From MSDN

Best Practices To ensure proper versioning behavior, follow these rules when modifying a type from version to version:

  • When adding a new serialized field, apply the OptionalFieldAttribute attribute.

  • When removing a NonSerializedAttribute attribute from a field (that was not serializable in a previous version), apply the OptionalFieldAttribute attribute.

  • For all optional fields, set meaningful defaults using the serialization callbacks unless 0 or null as defaults are acceptable.

I have tried to simulate your case where in new version of class have new member named Element2. initialized my new member to "This is new member" here is full proof

Test1 assumes you serialized with old definition of Root class with Just one Element1

Test2 when you serialized and de serialized with new definition of Root Class

To answer your question any way to provide default values you should use "OptionalField"

using System;
using System.Runtime.Serialization;
using System.IO;

public class Test
{

  [Serializable]
  public class Root
  {
    [OptionalField(VersionAdded = 2)] // As recommended by Microsoft
    private string mElement2 = "This is new member";
    public String Element1 { get; set; }    
    public String Element2 { get { return mElement2; } set { mElement2 = value; } }
  }

  public static void Main(string[] s)
  {
    Console.WriteLine("Testing serialized with old definition of Root ");
    Console.WriteLine(" ");
    Test_When_Original_Object_Was_Serialized_With_One_Element();
    Console.WriteLine(" ");
    Console.WriteLine("Testing serialized with new definition of Root ");
    Console.WriteLine(" ");
    Test_When_Original_Object_Was_Serialized_With_Two_Element();
    Console.ReadLine();
  }

  private static void TestReadingObjects(string xml)
  {
    System.Xml.Serialization.XmlSerializer xmlSerializer =
    new System.Xml.Serialization.XmlSerializer(typeof(Root));


    System.IO.Stream stream = new MemoryStream();
    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
    Byte[] bytes = encoding.GetBytes(xml);
    stream.Write(bytes, 0, bytes.Length);
    stream.Position = 0;
    Root r = (Root)xmlSerializer.Deserialize(stream);

    Console.WriteLine(string.Format("Element 1 = {0}", r.Element1));

    Console.WriteLine(string.Format("Element 2 = {0}", r.Element2 == null ? "Null" : r.Element2));
  }
  private static void Test_When_Original_Object_Was_Serialized_With_One_Element()
  {
    TestReadingObjects(@"<Root>   <Element1>1</Element1>   </Root>");
  }

  private static void Test_When_Original_Object_Was_Serialized_With_Two_Element()
  {
    TestReadingObjects(@"<Root>   <Element1>1</Element1> <Element2>2</Element2>   </Root>");
  }
}

// here is the output enter image description here

Solution 3

You need to manually handle it using custom methods and marking them with the appropriate attributes OnSerializing/OnSerialized/OnDeserializing/OnDeserialized and manully determine how to initialize the values (if it can be done)

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializingattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializingattribute.aspx

a better way is to determine the version before hand and use a strategy pattern to do the deserialization. This isn't always possible so use what I suggest in that case.

Update: The previous answer is only applicable to binary serialization. For regular Xml you can use this method.

class Program
    {
        static void Main(string[] args)
        {
            Deserialize(@"..\..\v1.xml");
        }

        private static Model Deserialize(string file)
        {
            XDocument xdoc = XDocument.Load(file);
            var verAtt = xdoc.Root.Attribute(XName.Get("Version"));
            Model m = Deserialize<Model>(xdoc);

            IModelLoader loader = null;

            if (verAtt == null)
            {
                loader = GetLoader("1.0");
            }
            else
            {
                loader = GetLoader(verAtt.Value);
            }

            if (loader != null)
            {
                loader.Populate(ref m);
            }
            return m;
        }

        private static IModelLoader GetLoader(string version)
        {
            IModelLoader loader = null;
            switch (version)
            {
                case "1.0":
                    {
                        loader = new ModelLoaderV1();
                        break;
                    }
                case "2.0":
                    {
                        loader = new ModelLoaderV2();
                        break;
                    }
                case "3.0": { break; } //Current
                default: { throw new InvalidOperationException("Unhandled version"); }
            }
            return loader;
        }

        private static Model Deserialize<T>(XDocument doc) where T : Model
        {
            Model m = null;
            using (XmlReader xr = doc.CreateReader())
            {
               XmlSerializer xs = new XmlSerializer(typeof(T));
               m = (Model)xs.Deserialize(xr);
               xr.Close();
            }
            return m;
        }
    }

    public interface IModelLoader
    {
        void Populate(ref Model model);
    }

    public class ModelLoaderV1 : IModelLoader
    {
        public void Populate(ref Model model)
        {
            model.City = string.Empty;
            model.Phone = "(000)-000-0000";
        }
    }

    public class ModelLoaderV2 : IModelLoader
    {
        public void Populate(ref Model model)
        {
            model.Phone = "(000)-000-0000";
        }
    }

    public class Model
    {
        [XmlAttribute(AttributeName = "Version")]
        public string Version { get { return "3.0"; } set { } }
        public string Name { get; set; } //V1, V2, V3
        public string City { get; set; } //V2, V3
        public string Phone { get; set; } //V3 only

    }

Depending on your requirements, this could be simplified into a single method on the model (or a model loader).

This could also be used for model validation after deserialization.

Edit: you can serialize using the following code

 private static void Serialize(Model model)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Model));
            FileStream f = File.Create(@"..\..\v1.xml");
            xs.Serialize(f, model);

            f.Close();
        }

Solution 4

If you follow this pattern it's fairly straightforward:

  • Handle the serialization/deserialization yourself by implementing ISerializable
  • Use this to serialize both the members of your object and a serialization version number.
  • On the deserialization code, run a switch-case statement against the version number. When you start out, you'll just have one version - the initial deserialization code. As you go forward, you'll stamp a newer version number into newly serialized snapshots.
  • For future versions of your object, always leave the existing deserialization code intact, or modify it to map to members you rename / refactor, and primarily just add a new case statement for the new serialization version.

In this way, you will be able to successfully deserialize previous data even if the serialization snapshot was generated from a previous version of your assembly.

Solution 5

Use the [System.ComponentModel.DefaultValueAttribute] to define DefaultValues for Serialization.

Example from the MSDN:

private bool myVal=false;

[DefaultValue(false)]
 public bool MyProperty {
    get {
       return myVal;
    }
    set {
       myVal=value;
    }
 }

So if you DeSerialize and the Property is not filled it would use the defaultValue as Value and you can use your old XML to generate the new Object.

If there were Properties removed in the new Version this should go without any problems trough XMLSerialization. (as far as i know)

Share:
11,301

Related videos on Youtube

Mike Webb
Author by

Mike Webb

I am a full time software developer working for BizStream. I love what I do and am always excited for new and interesting projects. I love my wife, my kids, my life, helping and hanging out with others, and working for Jesus Christ as He has called me.

Updated on June 04, 2022

Comments

  • Mike Webb
    Mike Webb almost 2 years

    What I have is a custom settings file that I serialize/deserialize using an XmlSerializer. I have no schema defined and no serialization tags in my object definition, just straight object serialization (although I will add them if needed).

    My issue is that I need to add data members to the object. If I do that I know that the old settings file will not deserialize.

    Is there a way to specify default values for the added members or some simple way to ignore them if they are missing from the XML?

  • Dustin Davis
    Dustin Davis over 12 years
    This isn't fail safe. It would be way better to add a version attribute to the root and only check for the version number, then use a strategy parrten to deserialize from there. Your example is easily broken.
  • Surjit Samra
    Surjit Samra over 12 years
    That was just a simple sample to prove it is possible de serialize with new definition of class. I do know about onserialized attributes but i thought that was not his question here is full sample i did few days ago which runs through WCF to handle uninitialized member variables stackoverflow.com/questions/8084868/…
  • Surjit Samra
    Surjit Samra over 12 years
    May be you can provide some sample checking version number and then using startegy pattern
  • vittore
    vittore over 12 years
    you don't need in most of the cases however you can do it this way also.
  • vittore
    vittore over 12 years
    i think you overcomplicate things, using DataContracts and explicit marking which members are to be serialized, handling versioning of data contracts turns into piece of cake.
  • Dustin Davis
    Dustin Davis over 12 years
    You're forcing a WCF solution when there is no mention of WCF in his question.
  • Surjit Samra
    Surjit Samra over 12 years
    I am not forcing WCF solution, That is an example how you can override default of serialization and deserilization.He can skip WCF and look at how class Data is decorated with attributes and how overriding deserialization helps to provide default values.
  • Surjit Samra
    Surjit Samra over 12 years
    @DustinDavis I am still waiting for your fail safe version attribute checking and using strategy pattern ? This is a learning site not just criticise and then hide away
  • Dustin Davis
    Dustin Davis over 12 years
    Search engines are a wonderful thing, you should try them some time. See my updated answer.
  • Surjit Samra
    Surjit Samra over 12 years
    Now who needs search engine if we got masters like you. May be you should provide your source(as you said try search engine) with your answer as well for more knowledge :)
  • Dustin Davis
    Dustin Davis over 12 years
    if you have questions, comments or concerns about my answer, I'll be more than happy to answer them. Master? No.
  • Surjit Samra
    Surjit Samra over 12 years
    I will have once I am able to run it, May be if you can provide full solution where you first serialize as at the moment it is trying to find a file on my system which does not exists for obvious reason :(
  • Surjit Samra
    Surjit Samra over 12 years
    Can you please write steps how to use it. As with current setup it is coming back with "Unhandled version" for obvious reason as i dont think you have tested it by yourself
  • Dustin Davis
    Dustin Davis over 12 years
    Pasted the wrong verion of the model. It should have been a string, not an int.
  • Surjit Samra
    Surjit Samra over 12 years
    @DustinDavis Although I can see what you are trying to achieve but can you post your references which suggest this is the best way to go for.I am still not able to find any resources who advocate to control version the way you are suggesting. Here is best practices suggested by Microsoft msdn.microsoft.com/en-us/library/ms229752(v=vs.80).aspx
  • Surjit Samra
    Surjit Samra over 12 years
    Updated my answer following best practices from Microsoft MSDN here msdn.microsoft.com/en-us/library/ms229752(v=vs.80).aspx
  • Dustin Davis
    Dustin Davis over 12 years
    Nice one, I'll have to check out that attribute. But, you still aren't solving the OP's issue, providing default values when deserializing older values. XmlSerializer will deserialize a model even if it has elemtns that don't map.
  • Surjit Samra
    Surjit Samra over 12 years
    Did you check results of Test1
  • Dustin Davis
    Dustin Davis over 12 years
    @Surjit You're missing the point. The OP wants to set defaults for newly added members. I'm sorry that you don't understand the code. You can start with these to get up to speed. en.wikipedia.org/wiki/Strategy_pattern and amazon.com/3-0-Design-Patterns-Judith-Bishop/dp/059652773X/…
  • Dustin Davis
    Dustin Davis over 12 years
    @Surjit Furthermore, I never said this was the best way as there are many other ways he could go such as setting the defaults in the constructor without regard to versioning. Then the deserialize process will populate only elements that it can. No need for added complexity. My solution is a better way to solve the OP's problem compared to the solution you provided which doesn't address the default values issue. Your solution would not work as expected if the serialize process changed the order of the elements, easily done using [XmlElement(Order = 99)]
  • Surjit Samra
    Surjit Samra over 12 years
    @DustinDavis may be you need to careful while writing comments like " you don't understand the code" and you should not be forcing patterns just for the sake of it.
  • Surjit Samra
    Surjit Samra over 12 years
    @DustinDavis I think you misread my question when I asked to post references I mean is your solution industry recommended,I do not want reference to a desgin pattern book, I know enough desgin patterns to write my own book :). I did like your XmlElement(Order = 99) can you post some samples please.