Serializing a Nullable<DateTime> in to XML

Viewed 31537

I am trying to serialize a class several of the data-members are Nullable objects, here is a example

[XmlAttribute("AccountExpirationDate")]
public Nullable<DateTime> AccountExpirationDate 
{ 
  get { return userPrincipal.AccountExpirationDate; } 
  set { userPrincipal.AccountExpirationDate = value; } 
}

However at runtime I get the error

Cannot serialize member 'AccountExpirationDate' of type System.Nullable`1[System.DateTime]. XmlAttribute/XmlText cannot be used to encode complex types.

However I checked and Nullable is a SerializableAttribute. What am I doing wrong?

6 Answers

If you just want it to work, then perhaps:

using System;
using System.ComponentModel;
using System.Xml.Serialization;
public class Account
{
    // your main property; TODO: your version
    [XmlIgnore]
    public Nullable<DateTime> AccountExpirationDate {get;set;}

    // this is a shim property that we use to provide the serialization
    [XmlAttribute("AccountExpirationDate")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public DateTime AccountExpirationDateSerialized
    {
        get {return AccountExpirationDate.Value;}
        set {AccountExpirationDate = value;}
    }

    // and here we turn serialization of the value on/off per the value
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public bool ShouldSerializeAccountExpirationDateSerialized()
    {
        return AccountExpirationDate.HasValue;
    }

    // test it...
    static void Main()
    {
        var ser = new XmlSerializer(typeof(Account));
        var obj1 = new Account { AccountExpirationDate = DateTime.Today };
        ser.Serialize(Console.Out, obj1);
        Console.WriteLine();
        var obj2 = new Account { AccountExpirationDate = null};
        ser.Serialize(Console.Out, obj2);
    }
}

This will only include the attribute when there is a non-null value.

You can only serialize it as an XmlElement, not as an XmlAttribute, as the representation is too complex for an attribute. That's what the exception is telling you.

I've used something like this many times.

[XmlIgnore]
public Nullable<DateTime> AccountExpirationDate 
{ 
    get { return userPrincipal.AccountExpirationDate; } 
    set { userPrincipal.AccountExpirationDate = value; } 
}

///
/// <summary>Used for Xml Serialization</summary>
///
[XmlAttribute("AccountExpirationDate")]
public string AccountExpirationDateString
{
    get
    {
        return AccountExpirationDate.HasValue
            ? AccountExpirationDate.Value.ToString("yyyy/MM/dd HH:mm:ss.fff")
            : string.Empty;
    }
    set
    {
        AccountExpirationDate =
            !string.IsNullOrEmpty(value)
            ? DateTime.ParseExact(value, "yyyy/MM/dd HH:mm:ss.fff")
            : null;
    }
}

Define a Serializable that encapsulates your funcionality.

Here's are and example.

[XmlAttribute("AccountExpirationDate")]  
public SerDateTime AccountExpirationDate   
{   
  get { return _SerDateTime ; }   
  set { _SerDateTime = value; }   
}  


/// <summary>
/// Serialize DateTime Class (<i>yyyy-mm-dd</i>)
/// </summary>
public class SerDateTime : IXmlSerializable {
    /// <summary>
    /// Default Constructor when time is not avalaible
    /// </summary>
    public SerDateTime() { }
    /// <summary>
    /// Default Constructor when time is avalaible
    /// </summary>
    /// <param name="pDateTime"></param>
    public SerDateTime(DateTime pDateTime) {
        DateTimeValue = pDateTime;
    }

    private DateTime? _DateTimeValue;
    /// <summary>
    /// Value
    /// </summary>
    public DateTime? DateTimeValue {
        get { return _DateTimeValue; }
        set { _DateTimeValue = value; }
    }

    // Xml Serialization Infrastructure
    void IXmlSerializable.WriteXml(XmlWriter writer) {
        if (DateTimeValue == null) {
            writer.WriteString(String.Empty);
        } else {
            writer.WriteString(DateTimeValue.Value.ToString("yyyy-MM-dd"));
            //writer.WriteString(SerializeObject.SerializeInternal(DateTimeValue.Value));
        }
    }

    void IXmlSerializable.ReadXml(XmlReader reader) {
        reader.ReadStartElement();
        String ltValue = reader.ReadString();
        reader.ReadEndElement();
        if (ltValue.Length == 0) {
            DateTimeValue = null;
        } else {                
            //Solo se admite yyyyMMdd
            //DateTimeValue = (DateTime)SerializeObject.Deserialize(typeof(DateTime), ltValue);
            DateTimeValue = new DateTime(Int32.Parse(ltValue.Substring(0, 4)),
                                Int32.Parse(ltValue.Substring(5, 2)),
                                Int32.Parse(ltValue.Substring(8, 2)));                                    
        }
    }

    XmlSchema IXmlSerializable.GetSchema() {
        return (null);
    }
}
#endregion

Simple workaround, which exposes one more property and is not very clean, but for simple cases works

        public bool? Nullable { get; set; }

        [XmlAttribute("nullable")]
        public string NullableXml
        {
            get => Nullable == null ? null : (bool)Nullable ? "true" : "false";
            set => Nullable = value == null ? (bool?)null : value == "true" ? true : value == "false" ? false : throw new Exception($"value {value} is not allowed");
        }
Related