Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a value type nullable with .NET XmlSerializer?

Let's suppose I have this object:

[Serializable]
public class MyClass
{
    public int Age { get; set; }
    public int MyClassB { get; set; }
}
[Serializable]
public class MyClassB
{
    public int RandomNumber { get; set; }
}

The XmlSerializer will serialize the object like that:

<MyClass>
    <Age>0</age>
    <MyClassB>
        <RandomNumber>4234</RandomNumber>
    </MyClassB>
</MyClass>

How can I made the property Age nullable? IE: to not serialize the property Age when it's under 0?

I tried with the Nullable, but it serialize my object like that:

<MyClass>
    <Age d5p1:nil="true" />
    <MyClassB>
        <RandomNumber>4234</RandomNumber>
    </MyClassB>
</MyClass>    

By reading the MSDN documentation I found this:

You cannot apply the IsNullable property to a member typed as a value type because a value type cannot contain nullNothingnullptra null reference (Nothing in Visual Basic). Additionally, you cannot set this property to false for nullable value types. When such types are nullNothingnullptra null reference (Nothing in Visual Basic), they will be serialized by setting xsi:nil to true.

source: http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlelementattribute.isnullable.aspx

I understand a value type can't be set to null. A valuetype is always set to something. The serialization can't make the decision to serialize it or not based on it's current value.

I tried with the attributes, but it didn't work out. I tried creating an agecontainer object and manipulate it's serialization with attributes, but it didn't work out.

What I really want is:

<MyClass>
    <MyClassB>
        <RandomNumber>4234</RandomNumber>
    </MyClassB>
</MyClass>

When the property Age is below 0 (zero).


Looks like you'll have to implement custom serialization.

Yeah, that's what I though too, but I'd like to get away without it.

In the application, the object is much more complex, and I would like to not handle the serialization myself.

like image 815
Jean-Francois Avatar asked Mar 31 '09 21:03

Jean-Francois


People also ask

How do you make a value nullable?

If you want to use the default value of the underlying value type in place of null , use the Nullable<T>. GetValueOrDefault() method. At run time, if the value of a nullable value type is null , the explicit cast throws an InvalidOperationException.

How do you make a nullable in C#?

You can declare nullable types using Nullable<t> where T is a type. Nullable<int> i = null; A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. For example, Nullable<int> can be assigned any value from -2147483648 to 2147483647, or a null value.

What is nullable value type in C#?

C# provides a special data types, the nullable types, to which you can assign normal range of values as well as null values. For example, you can store any value from -2,147,483,648 to 2,147,483,647 or null in a Nullable<Int32> variable. Similarly, you can assign true, false, or null in a Nullable<bool> variable.

How does the XmlSerializer work C#?

The XmlSerializer creates C# (. cs) files and compiles them into . dll files in the directory named by the TEMP environment variable; serialization occurs with those DLLs. These serialization assemblies can be generated in advance and signed by using the SGen.exe tool.


5 Answers

I just discovered this. XmlSerialier looks for a XXXSpecified boolean property to determine if it should be included. This should solve the problem nicely.

[Serializable] public class MyClass {   public int Age { get; set; }   [XmlIgnore]   public bool AgeSpecified { get { return Age >= 0; } }   public int MyClassB { get; set; } }  [Serializable] public class MyClassB {   public int RandomNumber { get; set; } } 

Proof:

static string Serialize<T>(T obj) {   var serializer = new XmlSerializer(typeof(T));   var builder = new StringBuilder();   using (var writer = new StringWriter(builder))   {     serializer.Serialize(writer, obj);     return builder.ToString();   } }  static void Main(string[] args) {   var withoutAge = new MyClass() { Age = -1 };   var withAge = new MyClass() { Age = 20 };    Serialize(withoutAge); // = <MyClass><MyClassB>0</MyClassB></MyClass>   Serialize(withAge); // = <MyClass><Age>20</Age><MyClassB>0</MyClassB></MyClass> } 

Edit: Yes, it is a documented feature. See the MSDN entry for XmlSerializer

Another option is to use a special pattern to create a Boolean field recognized by the XmlSerializer, and to apply the XmlIgnoreAttribute to the field. The pattern is created in the form of propertyNameSpecified. For example, if there is a field named "MyFirstName" you would also create a field named "MyFirstNameSpecified" that instructs the XmlSerializer whether to generate the XML element named "MyFirstName".

like image 78
Samuel Avatar answered Sep 23 '22 07:09

Samuel


Extending Samuel's answer and Greg Beech's comment to the case of a boolean property: if the property is of type bool then you can't write a simple test in the propertySpecified property.

A solution is to use a Nullable<bool> type, then the test in the propertySpecified property is simply property.HasValue. e.g.

using System.Xml.Serialization;  public class Person {     public bool? Employed { get; set; }      [XmlIgnore]     public bool EmployedSpecified { get { return Employed.HasValue; } } } 

An alternative to using a nullable type for a numeric property (suggested by Greg Beech) is to set the value property to an invalid default value, such as -1, as follows:

using System.ComponentModel; using System.Xml.Serialization;  public class Person {     [DefaultValue(-1)]     public int Age { get; set; }      [XmlIgnore]     public bool AgeSpecified { get { return Age >= 0; } } } 
like image 37
jumpalongjim Avatar answered Sep 21 '22 07:09

jumpalongjim


You can use XmlElementAttribute.IsNullable:

[Serializable]
public class MyClass
{
    [XmlElement(IsNullable = true)]
    public int? Age { get; set; }

    public int MyClassB { get; set; }
}
like image 38
Yochai Timmer Avatar answered Sep 19 '22 07:09

Yochai Timmer


This should help Make Age int? and..

public bool ShouldSerializeAge() { return Age.HasValue; }

..it does mean adding the ShouldSerializeXXX methods to your class!

like image 45
Dog Ears Avatar answered Sep 21 '22 07:09

Dog Ears


You need to do custom XML serialization; see IXmlSerializer.

public class MyClass : IXmlSerializable
{
    public int Age { get; set; }
    public MyClassB MyClassB { get; set; }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        // http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        if (reader.IsStartElement("Age"))
            Age = reader.ReadContentAsInt();

        var serializer = new XmlSerializer(typeof(MyClassB));
        MyClassB = (MyClassB)serializer.Deserialize(reader);
    }

    public void WriteXml(XmlWriter writer)
    {
        if (Age > 0)
        {
            writer.WriteStartElement("Age");
            writer.WriteValue(Age);
            writer.WriteEndElement();
        }

        var serializer = new XmlSerializer(typeof(MyClassB));
        serializer.Serialize(writer, MyClassB);
    }
}
like image 21
Ðаn Avatar answered Sep 20 '22 07:09

Ðаn