Ben Griswold
Ben Griswold

Reputation: 18321

Suppress Null Value Types from Being Emitted by XmlSerializer

Please consider the following Amount value type property which is marked as a nullable XmlElement:

[XmlElement(IsNullable=true)] 
public double? Amount { get ; set ; }

When a nullable value type is set to null, the C# XmlSerializer result looks like the following:

<amount xsi:nil="true" />

Rather than emitting this element, I would like the XmlSerializer to suppress the element completely. Why? We're using Authorize.NET for online payments and Authorize.NET rejects the request if this null element exists.

The current solution/workaround is to not serialize the Amount value type property at all. Instead we have created a complementary property, SerializableAmount, which is based on Amount and is serialized instead. Since SerializableAmount is of type String, which like reference types are suppressed by the XmlSerializer if null by default, everything works great.

/// <summary>
/// Gets or sets the amount.
/// </summary>
[XmlIgnore]
public double? Amount { get; set; }

/// <summary>
/// Gets or sets the amount for serialization purposes only.
/// This had to be done because setting value types to null 
/// does not prevent them from being included when a class 
/// is being serialized.  When a nullable value type is set 
/// to null, such as with the Amount property, the result 
/// looks like: &gt;amount xsi:nil="true" /&lt; which will 
/// cause the Authorize.NET to reject the request.  Strings 
/// when set to null will be removed as they are a 
/// reference type.
/// </summary>
[XmlElement("amount", IsNullable = false)]
public string SerializableAmount
{
    get { return this.Amount == null ? null : this.Amount.ToString(); }
    set { this.Amount = Convert.ToDouble(value); }
}

Of course, this is just a workaround. Is there a cleaner way to suppress null value type elements from being emitted?

Upvotes: 68

Views: 45617

Answers (4)

Joseph K
Joseph K

Reputation: 1

public class XmlNullable<T>
    where T : struct
{
    [XmlText]
    public T Value { get; set; }

    public static implicit operator XmlNullable<T>(T value)
    {
        return new XmlNullable<T>
        {
            Value = value
        };
    }

    public static implicit operator T?(XmlNullable<T> value)
    {
        if (value == null)
        {
            return null;
        }

        return value.Value;
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

Tested successfully with

  • int
  • bool
  • enum with [XmlEnum] attribute
  • serialize and deserialize

Example:

[XmlRoot("example")]
public class Example
{
    [XmlElement("amount")]
    public XmlNullable<double> Amount { get; set; }
}

Null property ignored:

new Example()

will result in XML:

<example />

Property with value gets to XML, as before:

var example = new Example
{
    Amount = 3.14
};

will result in XML:

<example>
  <amount>3.14</amount>
</example>

Upvotes: 0

Nicolas Battaglia
Nicolas Battaglia

Reputation: 11

you can try this :

xml.Replace("xsi:nil=\"true\"", string.Empty);

Upvotes: -1

mko
mko

Reputation: 7325

There is also an alternative to get

 <amount /> instead of <amount xsi:nil="true" />

Use

[XmlElement("amount", IsNullable = false)]
public string SerializableAmount
{
    get { return this.Amount == null ? "" : this.Amount.ToString(); }
    set { this.Amount = Convert.ToDouble(value); }
}

Upvotes: 7

Marc Gravell
Marc Gravell

Reputation: 1062630

Try adding:

public bool ShouldSerializeAmount() {
   return Amount != null;
}

There are a number of patterns recognised by parts of the framework. For info, XmlSerializer also looks for public bool AmountSpecified {get;set;}.

Full example (also switching to decimal):

using System;
using System.Xml.Serialization;

public class Data {
    public decimal? Amount { get; set; }
    public bool ShouldSerializeAmount() {
        return Amount != null;
    }
    static void Main() {
        Data d = new Data();
        XmlSerializer ser = new XmlSerializer(d.GetType());
        ser.Serialize(Console.Out, d);
        Console.WriteLine();
        Console.WriteLine();
        d.Amount = 123.45M;
        ser.Serialize(Console.Out, d);
    }
}

More information on ShouldSerialize* on MSDN.

Upvotes: 139

Related Questions