Xml Deserialization of Collection Property with Code Defaults

XML Deserialization of collection property with code defaults

You are correct that many serializers (though not all) work this way. Json.NET does, its JsonConverter.ReadJson method actually has an Object existingValue for exactly this situation.

I don't know of any documents where these sorts of implementation details are spelled out. The easiest way to determine whether a serializer uses pre-allocated collections when present rather than unconditionally allocating and then setting one itself is to actually test it by using an ObservableCollection<T> and attaching debug listeners when it is changed:

[Serializable]
[DataContract]
public class TestConfiguration
{
[DataMember]
public String Name { get { return mName; } set { mName = value; } }

private String mName = "Pete Sebeck";

[DataMember]
public ObservableCollection<String> Associates
{
get
{
Debug.WriteLine(mAssociates == null ? "Associates gotten, null value" : "Associates gotten, count = " + mAssociates.Count.ToString());
return mAssociates;
}
set
{
Debug.WriteLine(value == null ? "Associates set to a null value" : "Associates set, count = " + value.Count.ToString());
RemoveListeners(mAssociates);
mAssociates = AddListeners(value);
}
}

private ObservableCollection<String> mAssociates = AddListeners(new ObservableCollection<string>() { "Jon", "Natalie" });

public override String ToString()
{
StringBuilder buffer = new StringBuilder();
buffer.AppendLine(String.Format("Name: {0}", Name));
buffer.AppendLine("Associates:");
foreach (String associate in mAssociates)
{
buffer.AppendLine(String.Format("\t{0}", associate));
}
return buffer.ToString();
}

static ObservableCollection<String> AddListeners(ObservableCollection<String> list)
{
if (list != null)
{
list.CollectionChanged -= list_CollectionChanged; // In case it was already there.
list.CollectionChanged += list_CollectionChanged;
}
return list;
}

static ObservableCollection<String> RemoveListeners(ObservableCollection<String> list)
{
if (list != null)
{
list.CollectionChanged -= list_CollectionChanged; // In case it was already there.
}
return list;
}

public static ValueWrapper<bool> ShowDebugInformation = new ValueWrapper<bool>(false);

static void list_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!ShowDebugInformation)
return;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Debug.WriteLine(string.Format("Added {0} items", e.NewItems.Count));
break;
case NotifyCollectionChangedAction.Move:
Debug.WriteLine("Moved items");
break;
case NotifyCollectionChangedAction.Remove:
Debug.WriteLine(string.Format("Removed {0} items", e.OldItems.Count));
break;
case NotifyCollectionChangedAction.Replace:
Debug.WriteLine("Replaced items");
break;
case NotifyCollectionChangedAction.Reset:
Debug.WriteLine("Reset collection");
break;
}
}
}

public static class TestTestConfiguration
{
public static void Test()
{
var test = new TestConfiguration();

Debug.WriteLine("\nTesting Xmlserializer...");
var xml = XmlSerializationHelper.GetXml(test);
using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
{
var testFromXml = XmlSerializationHelper.LoadFromXML<TestConfiguration>(xml);
Debug.WriteLine("XmlSerializer result: " + testFromXml.ToString());
}

Debug.WriteLine("\nTesting Json.NET...");
var json = JsonConvert.SerializeObject(test, Formatting.Indented);
using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
{
var testFromJson = JsonConvert.DeserializeObject<TestConfiguration>(json);
Debug.WriteLine("Json.NET result: " + testFromJson.ToString());
}

Debug.WriteLine("\nTesting DataContractSerializer...");
var contractXml = DataContractSerializerHelper.GetXml(test);
using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
{
var testFromContractXml = DataContractSerializerHelper.LoadFromXML<TestConfiguration>(contractXml);
Debug.WriteLine("DataContractSerializer result: " + testFromContractXml.ToString());
}

Debug.WriteLine("\nTesting BinaryFormatter...");
var binary = BinaryFormatterHelper.ToBase64String(test);
using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
{
var testFromBinary = BinaryFormatterHelper.FromBase64String<TestConfiguration>(binary);
Debug.WriteLine("BinaryFormatter result: " + testFromBinary.ToString());
}

Debug.WriteLine("\nTesting JavaScriptSerializer...");
var javaScript = new JavaScriptSerializer().Serialize(test);
using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
{
var testFromJavaScript = new JavaScriptSerializer().Deserialize<TestConfiguration>(javaScript);
Debug.WriteLine("JavaScriptSerializer result: " + testFromJavaScript.ToString());
}
}
}

I ran the test above, and found:

  1. XmlSerializer and Json.NET use the pre-existing collection if present. (In Json.NET this can be controlled by setting JsonSerializerSettings.ObjectCreationHandling to Replace)
  2. JavaScriptSerializer, BinaryFormatter and DataContractSerializer do not, and always allocate the collection themselves. For the latter two this is not surprising as both do not call default constructors and instead simply allocate empty memory directly.

I don't know why the serializers in case 1 behave this way. Perhaps their authors were concerned that the containing class might want to internally use a subclass of the collection being deserialized, or attach observers to observable collections as I have done, and so decided to honor that design?

One note - for all serializers (except, maybe, BinaryFormatter, about which I am unsure), if a collection property is declared specifically as an array then the serializer will allocate the array itself and set the array after it is fully populated. This means that arrays can always be used as proxy collections during serialization.

By using a proxy array, you can guarantee that your collection is overwritten during deserialization:

    [IgnoreDataMember]
[XmlIgnore]
[ScriptIgnore]
public ObservableCollection<String> { get; set; } // Or List<string> or etc.

[XmlArray("Associates")]
[DataMember(Name="Associates")]
public string[] AssociateArray
{
get
{
return (Associates == null ? null : Associates.ToArray());
}
set
{
if (Associates == null)
Associates = new ObservableCollection<string>();
Associates.Clear();
if (value != null)
foreach (var item in value)
Associates.Add(item);
}
}

Now the collection comes back with only the previously serialized members with all 5 serializers.

Xml Deserialization and default values

Ok, I've found a workaround that's solve my issue. I do the thing in two (in fact three) steps. First I deserialize the XML into my class. Second, I put all properties of the Specific classes to default values that I find in the SubClass by reflection, expect one field (the If of the Specific class). And third, I reload the XML into a DataSet. There, I look at the DataTable named Specific, and for all the properties of my class I look if there is any column of the same name, and if the cell contains a value I put it in my class.

Ouffffff !!!!
Not really beauty, but it works.

     DataSet xmlDS = new DataSet();
xmlDS.ReadXml(filename);
GetSpecifiedValuesInDataSet(xmlDS);

DataTable table = xmlDS.Tables["Specific"];

foreach(ArrayOfSubClass array in this.Items)
{
foreach(SubClass sub in array)
{
foreach(Specific specific in sub)
{
Type specificType = specific.GetType();

DataRow modelRow = null;
foreach(DataRow row in table.Rows)
{
if(row["Par1"].ToString().Equals(specific.Par1.ToString()))
{
modelRow = row;
break;
}
}

if(modelRow != null)
{
foreach(PropertyInfo propSpecific in specificType.GetProperties())
{
string propertyName = propSpecific .Name;
foreach(DataColumn col in table.Columns)
{
if(col.ColumnName.Equals(propertyName))
{
if(!string.IsNullOrEmpty(modelRow[propertyName].ToString()))
{
object value = Convert.ChangeType(modelRow[propertyName], propSpecific.PropertyType);
propSpecific.SetValue(modelProd, value, null);
}
}
}
}
}
}
}
}

And Voila

C# XML Deserialize ignore Default value attribute

Welcome to stackoverflow.

You've given very little information on how you are de-serializing them (a code snippet would help), but I assume you are de-serializing them into a typed object. If that is the case, then what you are experiencing is standard behavior. That is the whole point of the default value, you can't "partially de-serialize a typed object".

What you can do however is either:

  • de-serializing the object into a raw xmlObject, and write a custom serializer for it.

  • Make the properties nullable, this is typically where DTO's are useful.See below.

     public class SomeTypedObjectDTO
    {
    public Guid? NullableGuid { get; set; }
    public int? NullableInt { get; set; }
    }

----UPDATE----

I tired to keep this in line with your code. Lets say you have the following object.

public class DisplayPreferences
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Level { get; set; }
public bool CanDisplay { get; set; }
}

And lets say you only want to serialize the "CanDisplay" and the "level" properties. You can create a DTO (Data Transfer object) for it, which is basically a stripped down version of the original object.

DTO:

public class DisplayPreferencesDTO
{
public int Level { get; set; }
public bool CanDisplay { get; set; }
}

Then I believe all you need to do is change the generic typeof() to use the DTO instead of the actual object.

XmlSerializer serializer = new mlSerializer(typeof(DisplayPreferencesDTO));

You can then map the DTO back to the original object when you like and if you like. You can either do this mapping manually or use a framework called Automapper. Automapper was explicitly designed for mapping DTO's.

If you need me to clarify anything let me know.

Happy coding!

C# XML Deserialization W/ Default Values

FYI - i solved this with the IXMLSerializable interface. Note that this code is very specific to my needs in this one class, so YMMV.


public void WriteXml(XmlWriter writer)
{
foreach (PropertyInfo prop in GetType().GetProperties())
{
XmlIgnoreAttribute attr;
if (prop.TryGetAttribute(out attr))
continue;

if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition().Equals(typeof(List)))
{
XmlSerializer serializer = new XmlSerializer(prop.PropertyType, new XmlRootAttribute(prop.Name));
serializer.Serialize(writer, prop.GetValue(this, null));
}
else
{
writer.WriteElementString(prop.Name, prop.GetValue(this, null).ToString());
}
}
}

public void ReadXml(XmlReader reader)
{
if (reader.IsEmptyElement)
return;

XmlDocument xDoc = new XmlDocument();
xDoc.Load(reader);

Type type = GetType();

foreach (XmlNode node in xDoc.DocumentElement.ChildNodes)
{
PropertyInfo prop = type.GetProperty(node.Name);
if (prop != null && prop.CanWrite)
{
object value;
if (prop.PropertyType.IsEnum)
{
string stringValue = node.InnerText;
value = Enum.Parse(prop.PropertyType, stringValue);
}
else if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition().Equals(typeof(List)))
{
Type enumType = prop.PropertyType.GetGenericArguments()[0];
value = Activator.CreateInstance(prop.PropertyType);
var addMethod = value.GetType().GetMethod("Add");
foreach (XmlNode subNode in node.ChildNodes)
{
object enumValue = Enum.Parse(enumType, subNode.InnerText);
addMethod.Invoke(value, new[] { enumValue });
}
}
else
{
string stringValue = node.InnerText;
value = Convert.ChangeType(stringValue, prop.PropertyType);
}
prop.SetValue(this, value, null);
}
}
}

public XmlSchema GetSchema()
{
return null;
}

XML deserialization: object has default values

A concrete example (both c# ad xml) would go a very long way here. Most likely one of:

  • the names aren't an exact (case-sensitive) match between the xml and the c# (allowing for name overrides via attributes etc)
  • there is a mixup between xml attributes / elements
  • there is a difference in the xml namespaces (typically, present in the xml and missing in the c#)

With your edit, it becomes clearer. Xml namespaces are very significant; <foo xmlns="abc"/> and <foo/> are completely unrelated. Further, xml namespaces are inherited, so in:

<rootElem xmlns:cfg="namespace1" xmlns:office="namespace2" xmlns="namespace3">
<Prop1>6</Prop1>
<Prop2>string</Prop2>
</rootElem>

it is the case that Prop1 and Prop2 are in the namespace3 namespace that they inherit from their parent. To make it absolutely clear to the c# that you want those to be in the child namespace (rather than the empty namespace), tell it:

[Serializable]
[DesignerCategory("code")]
[XmlType(Namespace = Namespace3)]
[XmlRoot(Namespace = Namespace3, IsNullable = true)]
public partial class rootElem
{
private const string Namespace3 = "namespace3"; // to avoid repetition

[XmlElement(Namespace = Namespace3)]
public int Prop1 { get; set; }
}

Set default value while XML Deserialization C#

You have to initial property inside Contructor like Own Pauling

Or you can auto-initialize like this:

public class Person{
public string Name { get; set; } = "Some name"
public int Age { get; set; } = 18
}

On your code demo.

It just working when you set something (anything, even null) to the property before Deserialization.

C# deserialize a class - the default value of a variable

Yes it is possible, simply use the standard way to set standard values:

class Settings 
{
public string Field1 = "defaultvalue1";
public string Field2 = "defaultvalue2";
}
public void Main
{
Settings sts = (Settings)Deserialise(path);
/* not needed
foreach(var fld in typeof(sts))
{
if(fld.Value == null)
fld.Value = Settings.Fields[fld].DefaulValue;
}*/
}

XML Deserialization handle empty tags with Default values

Try following :

    public class OrderedItem
{
private DateTime _Startdate = new DateTime();
[XmlElement(ElementName = "startdate")]
public DateTime Startdate {
get{ return _Startdate;}
set{ _Startdate = value;}
}

private DateTime _Enddate = new DateTime();
[XmlElement(ElementName = "enddate")]
public DateTime Enddate
{
get { return _Enddate; }
set { _Enddate = value; }
}
}


Related Topics



Leave a reply



Submit