I have a c# class that has 20+ string properties. I set about a fourth of those to an actual value. I would like to serialize the class and get an output of
<EmptyAttribute></EmptyAttribute>
for a property
public string EmptyAttribute {get;set;}
I do not want the output to be
<EmptyAttribute xsi:nil="true"></EmptyAttribute>
I am using the following class
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(string filename) : base(filename,Encoding.UTF8) { }
public override void WriteEndElement()
{
base.WriteFullEndElement();
base.WriteRaw(Environment.NewLine);
}
}
so that I can get the full tags. I just don't know how to get rid of the xsi:nil.
The way to have the XmlSerializer serialize a property without adding the xsi:nil="true" attribute is shown below:
[XmlRoot("MyClassWithNullableProp", Namespace="urn:myNamespace", IsNullable = false)]
public class MyClassWithNullableProp
{
public MyClassWithNullableProp( )
{
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:myNamespace") // Default Namespace
});
}
[XmlElement("Property1", Namespace="urn:myNamespace", IsNullable = false)]
public string Property1
{
get
{
// To make sure that no element is generated, even when the value of the
// property is an empty string, return null.
return string.IsNullOrEmpty(this._property1) ? null : this._property1;
}
set { this._property1 = value; }
}
private string _property1;
// To do the same for value types, you need a "helper property, as demonstrated below.
// First, the regular property.
[XmlIgnore] // The serializer won't serialize this property properly.
public int? MyNullableInt
{
get { return this._myNullableInt; }
set { this._myNullableInt = value; }
}
private int? _myNullableInt;
// And now the helper property that the serializer will use to serialize it.
[XmlElement("MyNullableInt", Namespace="urn:myNamespace", IsNullable = false)]
public string XmlMyNullableInt
{
get
{
return this._myNullableInt.HasValue?
this._myNullableInt.Value.ToString() : null;
}
set { this._myNullableInt = int.Parse(value); } // You should do more error checking...
}
// Now, a string property where you want an empty element to be displayed, but no
// xsi:nil.
[XmlElement("MyEmptyString", Namespace="urn:myNamespace", IsNullable = false)]
public string MyEmptyString
{
get
{
return string.IsNullOrEmpty(this._myEmptyString)?
string.Empty : this._myEmptyString;
}
set { this._myEmptyString = value; }
}
private string _myEmptyString;
// Now, a value type property for which you want an empty tag, and not, say, 0, or
// whatever default value the framework gives the type.
[XmlIgnore]
public float? MyEmptyNullableFloat
{
get { return this._myEmptyNullableFloat; }
set { this._myEmptyNullableFloat = value; }
}
private float? _myEmptyNullableFloat;
// The helper property for serialization.
public string XmlMyEmptyNullableFloat
{
get
{
return this._myEmptyNullableFloat.HasValue ?
this._myEmptyNullableFloat.Value.ToString() : string.Empty;
}
set
{
if (!string.IsNullOrEmpty(value))
this._myEmptyNullableFloat = float.Parse(value);
}
}
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Namespaces
{
get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;
}
Now, instantiate this class and serialize it.
// I just wanted to show explicitly setting all the properties to null...
MyClassWithNullableProp myClass = new MyClassWithNullableProp( ) {
Property1 = null,
MyNullableInt = null,
MyEmptyString = null,
MyEmptyNullableFloat = null
};
// Serialize it.
// You'll need to setup some backing store for the text writer below...
// a file, memory stream, something...
XmlTextWriter writer = XmlTextWriter(...) // Instantiate a text writer.
XmlSerializer xs = new XmlSerializer(typeof(MyClassWithNullableProp),
new XmlRootAttribute("MyClassWithNullableProp") {
Namespace="urn:myNamespace",
IsNullable = false
}
);
xs.Serialize(writer, myClass, myClass.Namespaces);
After retrieving the contents of the XmlTextWriter, you should have the following output:
<MyClassWithNullableProp>
<MyEmptyString />
<MyEmptyNullableFloat />
</MyClassWithNullableProp>
I hope this clearly demonstrates how the built-in .NET Framework XmlSerializer can be used to serialize properties to an empty element, even when the property value is null (or some other value you don't want to serialize). In addition, I have shown how you can make sure that null properties are not serialized at all. One thing to note, if you apply an XmlElementAttribute and set the IsNullable property of that attribute to true, then that property will serialize with the xsi:nil attribute when the property is null (unless overriden somewhere else).
I was actually able to figure this out. I know its a bit of a hack in some ways but this is how I got it to work
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(header.GetType());
XmlTextWriterFull writer = new XmlTextWriterFull(FilePath);
x.Serialize(writer, header);
writer.Flush();
writer.BaseStream.Dispose();
string xml = File.ReadAllText(FilePath);
xml = xml.Replace(" xsi:nil=\"true\"", "");
File.WriteAllText(FilePath, xml);
Hope this helps someone else out
Related
I know that the same problem is faced by a lot of people in one way or another but what I'm confused about is that how come Newtonsoft JSON Serializer is able to correctly handle this case while JavaScriptSerializer fails to do so.
I'm going to use the same code sample used in one of the other stackoverflow thread (JavascriptSerializer serializing property twice when "new" used in subclass)
void Main()
{
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var json = serializer.Serialize(new Limited());
Limited status = serializer.Deserialize<Limited>(json); --> throws AmbiguousMatchException
}
public class Full
{
public String Stuff { get { return "Common things"; } }
public FullStatus Status { get; set; }
public Full(bool includestatus)
{
if(includestatus)
Status = new FullStatus();
}
}
public class Limited : Full
{
public new LimitedStatus Status { get; set; }
public Limited() : base(false)
{
Status = new LimitedStatus();
}
}
public class FullStatus
{
public String Text { get { return "Loads and loads and loads of things"; } }
}
public class LimitedStatus
{
public String Text { get { return "A few things"; } }
}
But if I use Newtonsoft Json Serializer, everythings works fine. Why? And is it possible to achieve the same using JavaScriptSerializer?
void Main()
{
var json = JsonConvert.SerializeObject(new Limited());
Limited status = JsonConvert.DeserializeObject<Limited>(json); ----> Works fine.
}
The reason this works in Json.NET is that it has specific code to handle this situation. From JsonPropertyCollection.cs:
/// <summary>
/// Adds a <see cref="JsonProperty"/> object.
/// </summary>
/// <param name="property">The property to add to the collection.</param>
public void AddProperty(JsonProperty property)
{
if (Contains(property.PropertyName))
{
// don't overwrite existing property with ignored property
if (property.Ignored)
return;
JsonProperty existingProperty = this[property.PropertyName];
bool duplicateProperty = true;
if (existingProperty.Ignored)
{
// remove ignored property so it can be replaced in collection
Remove(existingProperty);
duplicateProperty = false;
}
else
{
if (property.DeclaringType != null && existingProperty.DeclaringType != null)
{
if (property.DeclaringType.IsSubclassOf(existingProperty.DeclaringType))
{
// current property is on a derived class and hides the existing
Remove(existingProperty);
duplicateProperty = false;
}
if (existingProperty.DeclaringType.IsSubclassOf(property.DeclaringType))
{
// current property is hidden by the existing so don't add it
return;
}
}
}
if (duplicateProperty)
throw new JsonSerializationException("A member with the name '{0}' already exists on '{1}'. Use the JsonPropertyAttribute to specify another name.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName, _type));
}
Add(property);
}
As you can see above, there is specific code here to prefer derived class properties over base class properties of the same name and visibility.
JavaScriptSerializer has no such logic. It simply calls Type.GetProperty(string, flags)
PropertyInfo propInfo = serverType.GetProperty(memberName,
BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
This method is documented to throw an exception in exactly this situation:
Situations in which AmbiguousMatchException occurs include the following:
A type contains two indexed properties that have the same name but different numbers of parameters. To resolve the ambiguity, use an overload of the GetProperty method that specifies parameter types.
A derived type declares a property that hides an inherited property with the same name, using the new modifier (Shadows in Visual Basic). To resolve the ambiguity, include BindingFlags.DeclaredOnly to restrict the search to members that are not inherited.
I don't know why Microsoft didn't add logic for this to JavaScriptSerializer. It's really a very simple piece of code; perhaps it got eclipsed by DataContractJsonSerializer?
You do have a workaround, which is to write a custom JavaScriptConverter:
public class LimitedConverter : JavaScriptConverter
{
const string StuffName = "Stuff";
const string StatusName = "Status";
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var limited = new Limited();
object value;
if (dictionary.TryGetValue(StuffName, out value))
{
// limited.Stuff = serializer.ConvertToType<string>(value); // Actually it's get only.
}
if (dictionary.TryGetValue(StatusName, out value))
{
limited.Status = serializer.ConvertToType<LimitedStatus>(value);
}
return limited;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var limited = (Limited)obj;
if (limited == null)
return null;
var dict = new Dictionary<string, object>();
if (limited.Stuff != null)
dict.Add(StuffName, limited.Stuff);
if (limited.Status != null)
dict.Add(StatusName, limited.Status);
return dict;
}
public override IEnumerable<Type> SupportedTypes
{
get { return new [] { typeof(Limited) } ; }
}
}
And then use it like:
try
{
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new LimitedConverter() });
var json = serializer.Serialize(new Limited());
Debug.WriteLine(json);
var status = serializer.Deserialize<Limited>(json);
var json2 = serializer.Serialize(status);
Debug.WriteLine(json2);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString()); // NO ASSERT.
}
How can I deserialize the string based on what I have done in this method? Basically, what I have here is to pass the string through the network using serialization and deserialize the string in order to convey the message. But once I managed to receive the message, I have no idea if what I'm doing is correct. Here's the code:
string ConvertToString(FrogGame np, Frog1 pm, Frog2 pm2) //Serialization. the three parameters are the classes.
{
XmlSerializer sendSerializer = new XmlSerializer(typeof(FrogGame),new Type[]{typeof(Frog1),typeof(Frog2)});
StreamWriter myWriter = new StreamWriter(#"pad1.xml");
sendSerializer.Serialize(myWriter, np);
sendSerializer.Serialize(myWriter, pm);
sendSerializer.Serialize(myWriter, pm2);
return myWriter.ToString();
} //Overall, I serialize it into string
Once I pass the string through the network, I want to deserialize it in order the pass the message to the classes. How do I continue here onwards? How can I edit? The code:
void StringReceived(string str) //so str is myWriter.ToString()
{
XmlSerializer revSerializer = new XmlSerializer(typeof(FrogGame), new Type[] { typeof(Frog1), typeof(Frog2) });
FileStream myFileStream = new FileStream(#"pad1.xml", FileMode.Open);
FrogGame b = (FrogGame)revSerializer.Deserialize(myFileStream);
if (b is Frog1)
{
if (Network.IsServer())
{
pm = (Frog1)b;
pm.Position.Y = b.pm.Position.Y;
pm.Position.X = b.pm.Position.X;
}
else
{
System.Diagnostics.Debug.WriteLine("BAD Message: " + msg);
}
}
else if (b is Frog2)
{
if (Network.IsClient())
{
pm2 = (PaddleMessage2)b;
pm2.Position.Y = b.pm2.Position.Y;
pm2.Position.X = b.pm2.Position.X;
}
else
{
System.Diagnostics.Debug.WriteLine("BAD Message: " + msg);
}
}
}
I might misinterpret your problem, but I why don't you put all the thing you want to save in a class and do it like this (plus, if you use class, your data "transportation" and "management" will be much easier) :
SERIALIZATION
XmlSerializer serializer = new XmlSerializer(typeof(FrogGameData));
TextWriter textWriter = new StreamWriter("FrogGameSaveFile.xml");
serializer.Serialize(textWriter, _frogGameData);
textWriter.Close();
DESERIALIZATION
XmlSerializer deserializer = new XmlSerializer(typeof(FrogGameData));
TextReader textReader = new StreamReader("FrogGameSaveFile.xml");
_frogGameData = (FrogGameData)deserializer.Deserialize(textReader);
textReader.Close();
Note : The need-to-be-saved field should have property, because the tag in the XML will mimic the property name.
Additional Note : FrogGameData is not different than a normal class for automatic serialization like this. The XML will mimic your property order in the class for the one in the XML file.
But if you wanna need to rearrange the XML tag placement, you could do something like [XmlElement(Order = 1)],[XmlElement(Order = 2)], etc on top of your property to customize the order in XML file.
UPDATE
In case you need it, this is an example of your FrogGameData class :
public class FrogGameData
{
private Frog _frog1;
private Frog _frog2;
public Frog Frog1
{
get { return _frog1; }
set { _frog1 = value; }
}
public Frog Frog2
{
get { return _frog2; }
set { _frog2 = value; }
}
}
And the XML will pretty much like this :
<?xml version="1.0" encoding="utf-8"?>
<FrogGameData>
<Frog1>Something-depends-on-your-data</Frog1>
<Frog2>Something-depends-on-your-data</Frog2>
</FrogGameData>
But, if your class is (Note the XmlElement part) :
public class FrogGameData
{
private Frog _frog1;
private Frog _frog2;
[XmlElement(Order = 2)]
public Frog Frog1
{
get { return _frog1; }
set { _frog1 = value; }
}
[XmlElement(Order = 1)]
public Frog Frog2
{
get { return _frog2; }
set { _frog2 = value; }
}
}
Then, your XML will be :
<?xml version="1.0" encoding="utf-8"?>
<FrogGameData>
<Frog2>Something-depends-on-your-data</Frog2>
<Frog1>Something-depends-on-your-data</Frog1>
</FrogGameData>
I have the following code:
[Serializable]
public class CustomClass
{
public CustomClass()
{
this.Init();
}
public void Init()
{
foreach (PropertyInfo p in this.GetType().GetProperties())
{
DescriptionAttribute da = null;
DefaultValueAttribute dv = null;
foreach (Attribute attr in p.GetCustomAttributes(true))
{
if (attr is DescriptionAttribute)
{
da = (DescriptionAttribute) attr;
}
if (attr is DefaultValueAttribute)
{
dv = (DefaultValueAttribute) attr;
}
}
UInt32 value = 0;
if (da != null && !String.IsNullOrEmpty(da.Description))
{
value = Factory.Instance.SelectByCode(da.Description, 3);
}
if (dv != null && value == 0)
{
value = (UInt32) dv.Value;
}
p.SetValue(this, value, null);
}
}
private UInt32 name;
[Description("name")]
[DefaultValue(41)]
public UInt32 Name
{
get { return this.name; }
set { this.name = value; }
}
(30 more properties)
}
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
<CustomClass />
And when I remove Init from the constructor it works as expected! I will get the full xml representation of the class but ofcourse without values (all with value 0).
<CustomClass>
<Name>0</Name>
...
</CustomClass>
Also, when I comment out the body of Init, I will get the same as above (the one with default values)
I've tried it with a public method, with a Helper class everything, but it does not work. That is, instead of the expected:
<CustomClass>
<Name>15</Name>
...
</CustomClass>
I will get
<CustomClass />
It seems when I use reflection in this class, serialization is not possible.
Or to summarize: when I call Init or when I fill my properties with reflection -> Serialization fails, when I remove this code part -> Serialization works but of course without my values.
Is this true? And does somebody know an alternative for my solution?
It should automatically get something from the database based on the Description and when this returns nothing it falls back to the DefaultValue...
PS1: I am using the XmlSerializer
PS2: When I set a breakpoint before the serialization, I can see that all the properties are filled with the good values (like 71, 72 etc).
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
XmlSerializer uses DefaultValue to decide which values to serialize - if it matches the default value, it doesn't store it. This approach is consistent with similar models such as data-binding / model-binding.
Frankly, I would say that in this case both DefaultValueAttribute and DescriptionAttribute are poor choices. Write your own - perhaps EavInitAttribute - then use something like:
[EavInit(41, "name")]
public uint Name {get;set;}
Note that there are other ways of controlling this conditional serialization - you could write a method like:
public bool ShouldSerializeName() { return true; }
which will also work to convince it to write the value (this is another pattern recognised by various serialization and data-binding APIs) - but frankly this is even more work (it is per-property, and needs to be public, so it makes a mess of the API).
Finally, I would say that hitting the database multiple times (once per property) for every new object construction is very expensive - especially since many of those values are likely to be assigned values in a moment anyway (so looking them up is wasted effort). I would put a lot of thought into making this both "lazy" and "cached" if it was me.
An example of a lazy and "sparse" implementation:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;
static class Program
{
static void Main()
{
var obj = new CustomClass();
Console.WriteLine(obj.Name);
// show it working via XmlSerializer
new XmlSerializer(obj.GetType()).Serialize(Console.Out, obj);
}
}
public class CustomClass : EavBase
{
[EavInit(42, "name")]
public uint Name
{
get { return GetEav(); }
set { SetEav(value); }
}
}
public abstract class EavBase
{
private Dictionary<string, uint> values;
protected uint GetEav([CallerMemberName] string propertyName = null)
{
if (values == null) values = new Dictionary<string, uint>();
uint value;
if (!values.TryGetValue(propertyName, out value))
{
value = 0;
var prop = GetType().GetProperty(propertyName);
if (prop != null)
{
var attrib = (EavInitAttribute)Attribute.GetCustomAttribute(
prop, typeof(EavInitAttribute));
if (attrib != null)
{
value = attrib.DefaultValue;
if (!string.IsNullOrEmpty(attrib.Key))
{
value = LookupDefaultValueFromDatabase(attrib.Key);
}
}
}
values.Add(propertyName, value);
}
return value;
}
protected void SetEav(uint value, [CallerMemberName] string propertyName = null)
{
(values ?? (values = new Dictionary<string, uint>()))[propertyName] = value;
}
private static uint LookupDefaultValueFromDatabase(string key)
{
// TODO: real code here
switch (key)
{
case "name":
return 7;
default:
return 234;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
protected class EavInitAttribute : Attribute
{
public uint DefaultValue { get; private set; }
public string Key { get; private set; }
public EavInitAttribute(uint defaultValue) : this(defaultValue, "") { }
public EavInitAttribute(string key) : this(0, key) { }
public EavInitAttribute(uint defaultValue, string key)
{
DefaultValue = defaultValue;
Key = key ?? "";
}
}
}
public class Warning
{
public Warning()
{
}
public Warning(String name, List<String> conflictList)
{
Name = name;
ConflictList = conflictList;
}
public string Name
{
get;
set;
}
public List<String> ConflictList
{
get;
set;
}
public static Warning ReadXML(string xml)
{
Warning warning = null;
try
{
XmlSerializer serializer = new XmlSerializer(typeof(Warning));
using (StringReader sr = new StringReader(xml))
{
XmlTextReader xtr = new XmlTextReader(sr);
warning = (Warning)serializer.Deserialize(xtr);
xtr.Close();
sr.Close();
}
}
catch
{
}
return warning;
}
public void Save(string filename)
{
XmlSerializer serializer = new XmlSerializer(typeof(Warning));
using (StreamWriter writer = new StreamWriter(filename))
{
serializer.Serialize(writer, this);
writer.Close();
}
}
}
If I deserializing the following xml string and I get non-null values back for both Name and ConflictList. This is fine and what I expect.
Warning w1 = Warning.ReadXML(
"<Warning><Name>test warning</Name><ConflictList><string>file1.txt</string></ConflictList></Warning>");
w1.Name returns "test warning" and w1.ConflictList returns a list containing the text "file1.txt"
However, if I deserialize the following xml string, w2.Name returns null, which i understand, but ConflictList is actually a list with a Count of 0. I expected it to be null as well. How come?
Warning w2 = Warning.ReadXML("<Warning></Warning>");
I am sure in some cases w1.ConflictList can return null if the element is not present in the xml string, but when does this happen?
I too would have expected it to leave it alone if it isn't in the xml, but I too can reproduce what you are saying, both "as is", and with [XmlElement(...)] on the member. I guess internally it is newing up the list "in case".
I also tried the alternative syntax just using a get and a lazy initializer, i.e.
// note: doesn't work; see answer text
private List<string> conflictList;
public List<String> ConflictList
{
get { return conflictList ?? (conflictList = new List<string>()); }
}
but it still invokes this getter, even when no conflict data is included. And annoyingly the *Specified pattern only ever gets called for data that is specified - it doesn't get called for data that isn't specified, else you could do:
// note: doesn't work; see answer text
[XmlIgnore]
public bool ConflictListSpecified
{
get { return ConflictList != null; }
set { if (!value) ConflictList = null; }
}
Add to that the fact that serialization callbacks aren't supported in XmlSerializer and I'm out of options.
I've tested it back to .NET 2.0, and it behaves the same there too...
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?
If you use an array instead of a list it works as expected
public class Command
{
[XmlArray(IsNullable = true)]
public Parameter[] To { get; set; }
}
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.
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; }
}
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; } }
}
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();
}