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?
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
It should deserialize just fine, it will just use the default constructor to initialize the items. So they will remain unchanged.
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();
}
If you follow this pattern it's fairly straightforward:
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.
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)
You can user ExtendedXmlSerializer . This serializer support deserialization old version of xml. Here is an example of deserialize old version of xml
You can even read different versions of an object from one file.
.NET provide quite a lot for serializing/deserializing and versioning.
1) user DataContract / DataMember attributes and DataContractSerializer
2) according to MSDN these changes are breaking
3) When a type with an extra field is deserialized into a type with a missing field, the extra information is ignored.
4) When a type with a missing field is deserialized into a type with an extra field, the extra field is left at its default value, usually zero or null.
5) Consider using IExtensibleDataObject for versioning
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.