Reputation: 21
<Customer>
<FirstName>SomeValue</FirstName>
<LastName>Somevalue</LastName>
<Address/>
</Customer>
I have the above xml, Address element is a complex object. When I am trying to deserialize to C# object, all the properties in the address object is set to null, but my requirement is to set the address object it self to null. Is there a way to achieve this?
Upvotes: 1
Views: 2168
Reputation: 116785
To confirm your question, you are asking: When deserializing XML, can I map an empty element to a null
property value, but map a non-empty element to a non-null
property value?
Assuming you are using XmlSerializer
, this is not implemented out of the box. XmlSerializer
will only map an element to a null
value when the attribute {http://www.w3.org/2001/XMLSchema}nil
, usually abbreviated xsi:nil
, has value "true"
. From Xsi:nil Attribute Binding Support:
The XmlSerializer class equates a true value for the nil attribute with a null reference, performing the following run-time conversions:
- When deserializing an XML document into objects: If the XmlSerializer class encounters an XML element that specifies xsi:nil="true", it assigns a null reference (if applicable) to the corresponding object.
- When serializing objects into an XML document: If the XmlSerializer class encounters a null reference for an object corresponding to an XML element, it either generates an element that specifies xsi:nil="true" or leaves the element out entirely, depending on whether a nillable="true" setting applies.
Since xsi:nil
is not present in your empty <Address>
element, it will not get mapped to null
automatically.
As a workaround, you could introduce an extra "proxy" property for Address
into Customer
that, during deserialization, checks to see if the Address
being set has default values, and if so, sets the underlying address value to null
. For instance, if your classes look like:
public class Address
{
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
You could do this in a couple ways:
With reflection. Introduce the following extension methods:
public static class ObjectExtensions
{
public static IEnumerable<object> Members(this object obj)
{
if (obj == null)
return new object[0];
var type = obj.GetType();
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetIndexParameters().Length == 0 && p.GetGetMethod(true) != null && p.CanRead)
.Select(p => p.GetValue(obj, new object[0]))
.Concat(type.GetFields().Select(f => f.GetValue(obj)));
}
public static bool IsDefault(this object obj)
{
if (obj == null)
return true;
var type = obj.GetType();
if (type.IsValueType)
{
return obj.Equals(Activator.CreateInstance(type, true));
}
return false;
}
public static bool MembersAreDefault(this object obj)
{
return obj.Members().All(v => v.IsDefault());
}
}
Then modify Customer
as follows:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
[XmlIgnore]
public Address Address { get; set; }
[XmlElement("Address")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public Address SerializableAddress
{
get
{
return Address;
}
set
{
if (value != null && value.MembersAreDefault())
value = null;
Address = value;
}
}
}
The property SerializableAddress
checks using reflection to see whether the incoming Address
has any non-default values. If not, the underlying property is set to null.
This solution is robust and reusable, but since it uses reflection, performance may not be great.
With a a standard interface to check whether any properties have been set in a given type. First, introduce the following:
public interface IHasDefaultMemberValues
{
bool MembersAreDefault();
}
Next, implement that interface for Address
:
public class Address : IHasDefaultMemberValues
{
// Properties as above.
#region IHasDefaultMemberValues Members
public virtual bool MembersAreDefault()
{
return Street1 == null && Street2 == null && City == null && State == null && Zip == null && Country == null;
}
#endregion
}
Finally, modify Customer
identically to solution #1.
This solution is performant, but depends upon MembersAreDefault()
being correctly maintained when new properties are added to Customer
.
Upvotes: 2
Reputation: 37
You can use the XmlIgnore attribute to exclude a property of your objects
[XmlIgnore]
public Address {get; set;}
Upvotes: -1