views:

2942

answers:

8

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).

A: 

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.

Jean-Francois
Make sure to update your original question with an edit instead of replying.
Robert P
+15  A: 

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".

Samuel
+1 that is a neat trick! Do you know if it's an undocumented feature or if it's fully supported?
James
@James that has been there before nullable, so it should work well :) I used it some time ago, and didn't run into any issues.
eglasius
But is it a documented (and hence, supported) feature?
Dan
y, right at the xml serializer class: http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx --- look for Specified, right above "Overriding Default Serialization"
eglasius
Yes it is documented, check my edit.
Samuel
Wow, that's a hack! Haha. But is does the job. And it's not too bad.Thanks for answer. Really appreciated. Clicked as "accepted answer".
Jean-Francois
Well, it's not really a hack and quite a useful feature if you don't want to write your own serializer.
Samuel
I actually prefer a very slight variation on this. Make age a nullable int, and then in the AgeSpecified property the condition is (Age != null). This allows you to get the required semantics without sentinel values such as zero.
Greg Beech
Quite a neat trick, glad I found this thread. Thanks!
Matt Olenik
A: 

This should help Make Age int? and..

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

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

Dog Ears
+2  A: 

You need to do custom XML serialization; see http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx">IXmlSerializer.

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

 #region IXmlSerializable Members

 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);
 }

 #endregion
}
Dan
A: 

You can always create a 'nullable' value type in c# like this

DataTime? MyDate = null;
int? CountNumber = null;

This gets replaced at compile-time with something like

Nullable<DateTime> MyDate = null;
Nullable<int> CountNumber = null;

(I know this does not help his question much, but it does address the title problem for anyone who sees this in a search engine)

JasonRShaver
-1: "anyone who sees this in a search engine" will be looking for an answer to the question, which this is not.
John Saunders
A: 

Forget about Nullable ... ShouldSerializeXXX is a pretty solution. Here Age will be serialized upon your condition.

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

    #region Conditional Serialization
    public bool ShouldSerializeAge() { return age > 0; }
    #endregion
}

[Serializable]
public class MyClassB
{
    public int RandomNumber { get; set; }
}
hoang
+2  A: 

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; } }
}
jumpalongjim
+1. Great addition to the discussion. Thank you fro sharing.
Jean-Francois
A: 

xsd.exe will autogenerate the XXXSpecified property and accessors if you set the 'minoccurs' attribute as 'minoccurs="0"' for an element ... if you are using a schema to define your xml/class

JustAsItSounds
-1: thanks for trying to answer the question, but it's clear from the question that the OP started with the objects, not the schema.
John Saunders