Aen Sidhe
Aen Sidhe

Reputation: 1171

How to deserialize null array to null in c#?

Here is my class:

public class Command
{
   [XmlArray(IsNullable = true)]
   public List<Parameter> To { get; set; }
}

When I serialize an object of this class:

var s = new XmlSerializer(typeof(Command));
s.Serialize(Console.Out, new Command());

it prints as expected (xml header and default MS namespaces are omitted):

<Command><To xsi:nil="true" /></Command>

When I took this xml and tried to deserialize it I got stucked, because it always print "Not null":

var t = s.Deserialize(...);
if (t.To == null)
    Console.WriteLine("Null");
else
    Console.WriteLine("Not null");

How to force deserializer to make my list null, if it is null in xml?

Upvotes: 11

Views: 3117

Answers (5)

NIKER
NIKER

Reputation: 474

For those who need it you can define the type as array with original element name and then wrap it, this will get you nullable list.

[XmlArray(ElementName = nameof(Metadata), IsNullable = true)]
public string[] MetadataArray { get; set; }

[XmlIgnore]
public List<string> Metadata
{
    get => this.MetadataArray?.ToList();
    set => this.MetadataArray = value?.ToArray();
}

Upvotes: 0

Justin
Justin

Reputation: 86759

If you use an array instead of a list it works as expected

public class Command
{
    [XmlArray(IsNullable = true)]
    public Parameter[] To { get; set; }
}

Upvotes: 5

Hans Passant
Hans Passant

Reputation: 941873

Ugh, annoying isn't it. You can see it being doing by running sgen.exe on your assembly with the /keep and /debug options so you can debug the deserialization code. It looks roughly like this:

global::Command o;
o = new global::Command();
if ((object)(o.@To) == null) o.@To = new global::System.Collections.Generic.List<global::Parameter>();
global::System.Collections.Generic.List<global::Parameter> a_0 = (global::System.Collections.Generic.List<global::Parameter>)o.@To;
// code elided
//...
while (Reader.NodeType != System.Xml.XmlNodeType.EndElement && Reader.NodeType != System.Xml.XmlNodeType.None) {
  if (Reader.NodeType == System.Xml.XmlNodeType.Element) {
    if (((object)Reader.LocalName == (object)id4_To && (object)Reader.NamespaceURI == (object)id2_Item)) {
      if (!ReadNull()) {
        if ((object)(o.@To) == null) o.@To = new global::System.Collections.Generic.List<global::Parameter>();
        global::System.Collections.Generic.List<global::Parameter> a_0_0 = (global::System.Collections.Generic.List<global::Parameter>)o.@To;
        // code elided
        //...
      }
      else {
        // Problem here:
        if ((object)(o.@To) == null) o.@To = new global::System.Collections.Generic.List<global::Parameter>();
        global::System.Collections.Generic.List<global::Parameter> a_0_0 = (global::System.Collections.Generic.List<global::Parameter>)o.@To;
      }
    }
  }
  Reader.MoveToContent();
  CheckReaderCount(ref whileIterations1, ref readerCount1);
}
ReadEndElement();
return o;

No less than 3 places where it makes sure the @To property isn't null. The first one is somewhat defensible, hard to deserialize data when the structure doesn't exist. The second one does the null test again, that's the only real good one. The third one is the problem, ReadNull() returned true but it still creates a non-null property value.

If you want to differentiate between empty and null then you have no good solution but edit this code by hand. Do this only if you are really desperate and the class is 100% stable. Well, don't do it. João's solution is the only good one.

Upvotes: 2

Jo&#227;o Angelo
Jo&#227;o Angelo

Reputation: 57718

If you really need that a collection is deserialized to null when no values are provided you can do it by not providing a set accessor, like this:

public class Command
{
    private List<Parameter> to;

    public List<Parameter> To { get { return this.to; } }
}

Upvotes: 0

Mikael Svenson
Mikael Svenson

Reputation: 39695

I agree with @Oliver's comment, but you can solve it like this if you absolutely need it to return null. Instead of using an automatic property, create your own backing field.

List<Parameter> _to;
public List<Parameter> To
{
    get
    {
        if (_to != null && _to.Count == 0) return null;
        return _to;
    }
    set { _to = value; }
}

Upvotes: 0

Related Questions