Conditional xml serialization
You should be able to use the ShouldSerialize*
pattern:
public class Book
{
[XmlAttribute]
public string Title {get;set;}
public bool ShouldSerializeTitle() {
return !string.IsNullOrEmpty(Title);
}
[XmlAttribute]
public string Description {get;set;}
public bool ShouldSerializeDescription() {
return !string.IsNullOrEmpty(Description );
}
[XmlAttribute]
public string Author {get;set;}
public bool ShouldSerializeAuthor() {
return !string.IsNullOrEmpty(Author);
}
[XmlAttribute]
public string Publisher {get;set;}
public bool ShouldSerializePublisher() {
return !string.IsNullOrEmpty(Publisher);
}
}
ShouldSerialize*() vs *Specified Conditional Serialization Pattern
The intent of the {propertyName}Specified
pattern is documented in XML Schema Binding Support: MinOccurs Attribute Binding Support. It was added to support an XSD schema element in which:
- The
<element>
element is involved. - minOccurs is zero.
- The maxOccurs attribute dictates a single instance.
- The data type converts to a value type.
In this case, xsd.exe /classes
will automatically generate (or you can manually generate) a property with the same name as the schema element and a {propertyName}Specified
boolean get/set property that tracks whether the element was encountered in the XML and should be serialized back to XML. If the element is encountered, {propertyName}Specified
is set to true
, otherwise false
. Thus the deserialized instance can determine whether the property was unset (rather than explicitly set to its default value) in the original XML.
The inverse is also implemented for schema generation. If you define a C# type with a pair of properties matching the pattern above, then use xsd.exe
to generate a corresponding XSD file, an appropriate minOccurrs
will be added to the schema. For instance, given the following type:
public class ExampleClass
{
[XmlElement]
public decimal Something { get; set; }
[XmlIgnore]
public bool SomethingSpecified { get; set; }
}
The following schema will be generated, and vice versa:
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="ExampleClass" nillable="true" type="ExampleClass" />
<xs:complexType name="ExampleClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Note that, while xsd.exe
is documented only to generate automatically a {propertyName}Specified
property for value type properties, XmlSerializer
will respect the pattern when used manually for reference type properties.
You might ask, why does xsd.exe
not bind to a Nullable<T>
in this case? Perhaps because:
- Nullables are used to support the
xsi:nil="true"
attribute instead. See Xsi:nil Attribute Binding Support. - Nullables were not introduced until .Net 2.0, so maybe it was too late to use them for this purpose?
You need to be aware of this pattern because xsd.exe
will sometimes generate it for you automatically, however the interaction between a property and its Specified
property is weird and liable to produce bugs. You can fill up all the properties in your class, then serialize to XML and lose everything because you didn't also set set the corresponding Specified
properties to true
. This "gotcha" comes up here from time to time here, see e.g. this question or this one also.
Another "gotcha" with this pattern is that, if you need to serialize your type with a serializer that does not support this pattern, you may want to manually suppress output of this property during serialization, and probably will need to manually set it during deserialization. Since each serializer may have its own custom mechanism for suppressing properties (or no mechanism at all!), doing this can become more and more burdensome over time.
(Finally, I'm a little surprised that your MyPropertySpecified
works successfully without a setter. I seem to recall a version of .Net 2.0 in which a missing {propertyName}Specified
setter would cause an exception to be thrown. But it's no longer reproducible on later versions, and I don't have 2.0 to test. So that might be a third gotcha.)
Support for the ShouldSerialize{PropertyName}()
method is documented in Properties in Windows Forms Controls: Defining Default Values with the ShouldSerialize and Reset Methods. As you can see the documentation is in the Windows Forms section of MSDN not the XmlSerializer
section, so it is, in fact, semi-hidden functionality. I have no idea why support for this method and the Specified
property both exist in XmlSerializer
. ShouldSerialize
was introduced in .Net 1.1 and I believe that MinOccurs binding support was added in .Net 2.0, so perhaps the earlier functionality didn't quite meet the needs (or taste) of the xsd.exe
development team?
Because it is a method not a property, it lacks the "gotchas" of the {propertyName}Specified
pattern. It also seems to be more popular in practice, and has been adopted by other serializers including:
- Json.NET
- protobuf-net (which claims to supports both patterns.)
So, which pattern to use?
If
xsd.exe
generates a{propertyName}Specified
property for you automatically, or your type needs to track whether a specific element appeared or not in the XML file, or you need your auto-generated XSD to indicate that a certain value is optional, use this pattern and watch out for the "gotchas".Otherwise, use the
ShouldSerialize{PropertyName}()
pattern. It has fewer gotchas and may be more widely supported.
XML Serializer ignore attribute depends on condition
You will need to wrap your nullable properties to get it work. For example, for your ValueA
public class Class
{
[XmlIgnore]
public decimal? ValueA { get; set; }
[XmlAttribute("ValueA")]
public decimal ValueAUnwrapped
{
//this will only called, when ShouldSerializeValueAUnwrapped return trues, so no NRE here
get => ValueA.Value;
set => ValueA = value;
}
public bool ShouldSerializeValueAUnwrapped() => ValueA.HasValue;
}
This code instructs serializer to serialize ValueAUnwrapped
property only when the original ValueA
property has value. This is achieved by adding ShouldSerialize<Name>()
function which serializer will call for corresponding Name
property: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/defining-default-values-with-the-shouldserialize-and-reset-methods?view=netframeworkdesktop-4.8
You will also need to pereform the same trick for the ValueB
.
Conditionally serialize/deserialize attribute
Your requirements match the propertyNameSpecified pattern of XmlSerializer
. From the docs:
If a schema includes an element that is optional ... [one] 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".
What's convenient about this pattern is that, beyond the documented behavior, during deserialization, XmlSerializer
will set the propertyNameSpecified to true if the property was encountered -- which is exactly what you need. Thus your class should look like:
public class Foo
{
private string myField;
private bool myFieldSerializes;
//Parameterless construction for serializing purposes
public Foo() { }
public Foo(string myField, bool myFieldSerializes)
{
this.myField = myField;
this.myFieldSerializes = myFieldSerializes;
}
[XmlElement(IsNullable = true)] // Emit a value even when null as long as MyFieldSpecified == true
public string MyField
{
get { return this.myField; }
set { this.myField = value; }
}
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool MyFieldSpecified { get { return myFieldSerializes; } set { myFieldSerializes = value; } }
}
(Adding [XmlElement(IsNullable = true)]
to your MyField
property ensures that an element will always be emitted when MyFieldSpecified == true
, even if the field itself is null
.)
Prototype fiddle.
Related Topics
Are .Net Switch Statements Hashed or Indexed
Sqlite .Net Performance, How to Speed Up Things
How to Add My New User Control to the Toolbox or a New Winform
Update Requires a Valid Updatecommand When Passed Datarow Collection with Modified Rows
Why Visual Studio Doesn't Create a Public Class by Default
Where Are Clr-Defined Methods Like [Delegate].Begininvoke Documented
Multiple Fields Validation Using Remote Validation
Extension Method on Enumeration, Not Instance of Enumeration
No Itemchecked Event in a Checkedlistbox
How to Get a Unique Identifier for a Device Within Windows 10 Universal
How to Ignore Get-Only Properties in JSON.Net Without Using JSONignore Attributes
How to Install a Windows Font Using C#
Publish a Project with Local Database
Return All Enumerables with Yield Return at Once; Without Looping Through
Should Idisposable.Dispose() Be Made Safe to Call Multiple Times
How to Convert a Simple .Net Console Project a into Portable Exe with Mono and Mkbundle