diff --git a/Directory.Build.props b/Directory.Build.props
index e5035ea..ab22976 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,8 +9,8 @@
ALTA Software llc.
Choice generator
ALTA Software llc.
- Copyright © 2024 ALTA Software llc.
- 2.1.5
+ Copyright © 2024-2026 ALTA Software llc.
+ 2.2.0
diff --git a/src/AltaSoft.Choice.Generator/Executor.cs b/src/AltaSoft.Choice.Generator/Executor.cs
index 2e82cc1..924f829 100644
--- a/src/AltaSoft.Choice.Generator/Executor.cs
+++ b/src/AltaSoft.Choice.Generator/Executor.cs
@@ -118,7 +118,14 @@ private static SourceCodeBuilder Process(INamedTypeSymbol typeSymbol, List x.TypeSymbol.IsValueType && !x.IsDateOnly()).Select(x => x.Name))
+ // Generate ShouldSerialize methods for all properties to prevent xsi:nil in XML
+ // This ensures that only the active choice property is serialized
+ foreach (var p in processedProperties)
{
- sb.AppendSummary($"Determines whether the property should be serialized.")
- .AppendBlock("returns", $"true if has a value; otherwise, false.");
+ sb.AppendSummary($"Determines whether the property should be serialized.")
+ .AppendBlock("returns", $"true if is the active choice; otherwise, false.");
sb.AppendLine("[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]");
- sb.Append("public bool ShouldSerialize").Append(p).Append("() => ");
+ sb.Append("public bool ShouldSerialize").Append(p.Name).Append("() => ");
+
if (isOnly1Property)
sb.AppendLine("true;");
else
- sb.Append(p).AppendLine(".HasValue;");
+ sb.Append("ChoiceType == ChoiceOf.").Append(p.Name).AppendLine(";");
+
sb.NewLine();
}
@@ -222,6 +238,14 @@ private static PropertyDetails ProcessProperty(IPropertySymbol propertySymbol)
var xmlTagAttribute = propertySymbol.GetAttributes().FirstOrDefault(x => x.AttributeClass?.ToDisplayString() == Constants.XmlTagAttributeFullName);
var xmlElementName = (string?)xmlTagAttribute?.ConstructorArguments[0].Value ?? propertySymbol.Name;
+ // Read the Namespace property from XmlTagAttribute if present
+ string? xmlNamespace = null;
+ var namespaceProperty = xmlTagAttribute?.NamedArguments.FirstOrDefault(x => x.Key == "Namespace");
+ if (namespaceProperty?.Value.Value is string ns)
+ {
+ xmlNamespace = ns;
+ }
+
var typeFullName = propertySymbol.Type.GetFullName();
var propertyName = propertySymbol.Name;
var modifiers = propertySymbol.GetModifiers();
@@ -231,6 +255,7 @@ private static PropertyDetails ProcessProperty(IPropertySymbol propertySymbol)
typeName: typeFullName.Replace("?", ""),
@namespace: propertySymbol.ContainingNamespace.ToDisplayString(),
xmlNameValue: xmlElementName,
+ xmlNamespace: xmlNamespace,
modifiers: modifiers,
summary: propertySymbol.GetSummaryText(),
getterAccessibility: propertySymbol.GetMethod?.DeclaredAccessibility ?? Accessibility.NotApplicable,
diff --git a/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs b/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs
index ee696a3..740c996 100644
--- a/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs
+++ b/src/AltaSoft.Choice.Generator/Models/PropertyDetails.cs
@@ -25,6 +25,11 @@ internal sealed class PropertyDetails
///
internal string XmlNameValue { get; private set; }
+ ///
+ /// The XML namespace for the property element.
+ ///
+ internal string? XmlNamespace { get; private set; }
+
///
/// The access modifiers for the property, such as "public", "private", etc.
///
@@ -66,6 +71,7 @@ public PropertyDetails(
string typeName,
string @namespace,
string xmlNameValue,
+ string? xmlNamespace,
string modifiers,
string? summary,
Accessibility getterAccessibility,
@@ -76,6 +82,7 @@ public PropertyDetails(
TypeName = typeName;
Namespace = @namespace;
XmlNameValue = xmlNameValue;
+ XmlNamespace = xmlNamespace;
Modifiers = modifiers;
Summary = summary;
GetterAccessibility = getterAccessibility;
diff --git a/src/AltaSoft.Choice/XmlTagAttribute.cs b/src/AltaSoft.Choice/XmlTagAttribute.cs
index f243614..14c3b40 100644
--- a/src/AltaSoft.Choice/XmlTagAttribute.cs
+++ b/src/AltaSoft.Choice/XmlTagAttribute.cs
@@ -16,6 +16,14 @@ public sealed class XmlTagAttribute : Attribute
///
public string Tag { get; }
+ ///
+ /// Gets the XML namespace associated with the property.
+ ///
+ ///
+ /// A representing the XML namespace, or null if no namespace is specified.
+ ///
+ public string? Namespace { get; set; }
+
///
/// Initializes a new instance of the class with the specified XML tag value.
///
diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs
index 7402982..099ef2e 100644
--- a/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs
+++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/ChoiceGeneratorTest.cs
@@ -107,29 +107,29 @@ public Task ChoiceTypeShouldNotGenerateImplicitMethodsAndCompileCorrectly()
using System.Xml.Serialization;
using AltaSoft.Choice;
using TestNamespace.OtherNamespace;
-
+
namespace TestNamespace
{
[Choice]
public sealed partial class Authorisation1Choice
{
-
+
///
/// Specifies the authorisation, in a coded form.
///
[XmlElement("Cd")]
-
+
public partial string? Code { get; set; }
-
+
///
/// Specifies the authorisation, in a free text form.
///
[XmlElement("Prtry")]
-
+
public partial Authorisation1Code? Proprietary { get; set; }
}
}
-
+
namespace TestNamespace.OtherNamespace
{
public enum Authorisation1Code
@@ -146,6 +146,86 @@ public enum Authorisation1Code
});
}
+ [Fact]
+ public Task ChoiceTypeShouldGenerateWithXmlTagNamespace()
+ {
+ const string source =
+ """
+ using System;
+ using System.Xml;
+ using System.Xml.Schema;
+ using System.Xml.Serialization;
+ using AltaSoft.Choice;
+
+ namespace TestNamespace
+ {
+ [Choice]
+ public sealed partial class XmlNamespaceChoice
+ {
+ ///
+ /// Specifies the code with namespace.
+ ///
+ [XmlTag("Cd", Namespace = "urn:test:code")]
+ public partial string? Code { get; set; }
+
+ ///
+ /// Specifies the proprietary value with namespace.
+ ///
+ [XmlTag("Prtry", Namespace = "urn:test:proprietary")]
+ public partial string? Proprietary { get; set; }
+ }
+ }
+ """;
+
+ return TestHelper.Verify(source, (_, x, _) =>
+ {
+ Assert.Single(x);
+ });
+ }
+
+ [Fact]
+ public Task ChoiceTypeShouldGenerateWithMixedXmlTagAndXmlElement()
+ {
+ const string source =
+ """
+ using System;
+ using System.Xml;
+ using System.Xml.Schema;
+ using System.Xml.Serialization;
+ using AltaSoft.Choice;
+
+ namespace TestNamespace
+ {
+ [Choice]
+ public sealed partial class MixedAttributeChoice
+ {
+ ///
+ /// Code with XmlTag and namespace.
+ ///
+ [XmlTag("Cd", Namespace = "urn:test:code")]
+ public partial string? Code { get; set; }
+
+ ///
+ /// Proprietary with standard XmlElement.
+ ///
+ [XmlElement("Prtry")]
+ public partial string? Proprietary { get; set; }
+
+ ///
+ /// Amount with XmlTag but no namespace.
+ ///
+ [XmlTag("Amt")]
+ public partial decimal? Amount { get; set; }
+ }
+ }
+ """;
+
+ return TestHelper.Verify(source, (_, x, _) =>
+ {
+ Assert.Single(x);
+ });
+ }
+
public static class TestHelper
{
internal static Task Verify(string source, Action, List, GeneratorDriver>? additionalChecks = null)
diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateAllMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateAllMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs
index bb31efb..0b26c26 100644
--- a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateAllMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs
+++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateAllMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs
@@ -168,10 +168,19 @@ public void Switch(
/// Determines whether the property should be serialized.
///
///
- /// true if has a value; otherwise, false.
+ /// true if is the active choice; otherwise, false.
///
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
- public bool ShouldSerializeCode() => Code.HasValue;
+ public bool ShouldSerializeCode() => ChoiceType == ChoiceOf.Code;
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeProprietary() => ChoiceType == ChoiceOf.Proprietary;
///
/// Choice enumeration
diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs
index 654e6f1..909e2d9 100644
--- a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs
+++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateDocumentationCorrectly_ForArrayInChoice#ArrayInTypeChoice.g.verified.cs
@@ -158,6 +158,24 @@ public void Switch(
return value is null ? null : CreateAsAccounts(value);
}
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeStringChoice() => ChoiceType == ChoiceOf.StringChoice;
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeAccounts() => ChoiceType == ChoiceOf.Accounts;
+
///
/// Choice enumeration
///
diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateWithMixedXmlTagAndXmlElement#MixedAttributeChoice.g.verified.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateWithMixedXmlTagAndXmlElement#MixedAttributeChoice.g.verified.cs
new file mode 100644
index 0000000..8574cc6
--- /dev/null
+++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateWithMixedXmlTagAndXmlElement#MixedAttributeChoice.g.verified.cs
@@ -0,0 +1,223 @@
+//HintName: MixedAttributeChoice.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft Choice.Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using TestNamespace;
+using AltaSoft.Choice;
+using System;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Xml;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace TestNamespace;
+
+#pragma warning disable CS8774 // Member must have a non-null value when exiting.
+#pragma warning disable CS0628 // New protected member declared in sealed type
+
+public sealed partial class MixedAttributeChoice
+{
+ ///
+ /// Constructor for Serialization/Deserialization
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public MixedAttributeChoice()
+ {
+ }
+
+ ///
+ /// Choice enum
+ ///
+ [JsonIgnore]
+ [XmlIgnore]
+ [ChoiceTypeProperty]
+ public ChoiceOf ChoiceType { get; private set; }
+
+ private string? _code;
+
+ ///
+ /// Code with XmlTag and namespace.
+ ///
+ [DisallowNull]
+ [XmlElement("Cd", Namespace = "urn:test:code")]
+ [ChoiceProperty]
+ public partial string? Code
+ {
+ get => _code;
+ set
+ {
+ _code = value ?? throw new InvalidOperationException("Choice value cannot be null");
+ _proprietary = null;
+ _amount = null;
+ ChoiceType = ChoiceOf.Code;
+ }
+ }
+
+ private string? _proprietary;
+
+ ///
+ /// Proprietary with standard XmlElement.
+ ///
+ [DisallowNull]
+ [XmlElement("Proprietary")]
+ [ChoiceProperty]
+ public partial string? Proprietary
+ {
+ get => _proprietary;
+ set
+ {
+ _proprietary = value ?? throw new InvalidOperationException("Choice value cannot be null");
+ _code = null;
+ _amount = null;
+ ChoiceType = ChoiceOf.Proprietary;
+ }
+ }
+
+ private decimal? _amount;
+
+ ///
+ /// Amount with XmlTag but no namespace.
+ ///
+ [DisallowNull]
+ [XmlElement("Amt")]
+ [ChoiceProperty]
+ public partial decimal? Amount
+ {
+ get => _amount;
+ set
+ {
+ _amount = value ?? throw new InvalidOperationException("Choice value cannot be null");
+ _code = null;
+ _proprietary = null;
+ ChoiceType = ChoiceOf.Amount;
+ }
+ }
+
+
+ ///
+ /// Creates a new instance and sets its value using the specified .
+ ///
+ /// The value to assign to the created choice instance.
+ public static TestNamespace.MixedAttributeChoice CreateAsCode(string value) => new () { Code = value };
+
+ ///
+ /// Creates a new instance and sets its value using the specified .
+ ///
+ /// The value to assign to the created choice instance.
+ public static TestNamespace.MixedAttributeChoice CreateAsProprietary(string value) => new () { Proprietary = value };
+
+ ///
+ /// Creates a new instance and sets its value using the specified .
+ ///
+ /// The value to assign to the created choice instance.
+ public static TestNamespace.MixedAttributeChoice CreateAsAmount(decimal value) => new () { Amount = value };
+
+ ///
+ /// Applies the appropriate function based on the current choice type
+ ///
+ /// The return type of the provided match functions
+ /// Function to invoke if the choice is a value
+ /// Function to invoke if the choice is a value
+ /// Function to invoke if the choice is a value
+ public TResult Match(
+ Func matchCode,
+ Func matchProprietary,
+ Func matchAmount)
+ {
+ return ChoiceType switch
+ {
+ ChoiceOf.Code => matchCode(Code!),
+ ChoiceOf.Proprietary => matchProprietary(Proprietary!),
+ ChoiceOf.Amount => matchAmount(Amount!.Value),
+ _ => throw new InvalidOperationException($"Invalid ChoiceType. '{ChoiceType}'")
+ };
+ }
+
+ ///
+ /// Applies the appropriate Action based on the current choice type
+ ///
+ /// Action to invoke if the choice is a value
+ /// Action to invoke if the choice is a value
+ /// Action to invoke if the choice is a value
+ public void Switch(
+ Action matchCode,
+ Action matchProprietary,
+ Action matchAmount)
+ {
+ switch (ChoiceType)
+ {
+ case ChoiceOf.Code:
+ matchCode(Code!);
+ return;
+
+ case ChoiceOf.Proprietary:
+ matchProprietary(Proprietary!);
+ return;
+
+ case ChoiceOf.Amount:
+ matchAmount(Amount!.Value);
+ return;
+
+ default:
+ throw new XmlException($"Invalid ChoiceType. '{ChoiceType}'");
+ }
+ }
+
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeCode() => ChoiceType == ChoiceOf.Code;
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeProprietary() => ChoiceType == ChoiceOf.Proprietary;
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeAmount() => ChoiceType == ChoiceOf.Amount;
+
+ ///
+ /// Choice enumeration
+ ///
+ [XmlType("ChoiceOf.MixedAttributeChoice")]
+ public enum ChoiceOf
+ {
+ ///
+ /// Code with XmlTag and namespace.
+ ///
+ Code,
+ ///
+ /// Proprietary with standard XmlElement.
+ ///
+ Proprietary,
+ ///
+ /// Amount with XmlTag but no namespace.
+ ///
+ Amount,
+ }
+}
diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateWithXmlTagNamespace#XmlNamespaceChoice.g.verified.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateWithXmlTagNamespace#XmlNamespaceChoice.g.verified.cs
new file mode 100644
index 0000000..268ab64
--- /dev/null
+++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldGenerateWithXmlTagNamespace#XmlNamespaceChoice.g.verified.cs
@@ -0,0 +1,173 @@
+//HintName: XmlNamespaceChoice.g.cs
+//------------------------------------------------------------------------------
+//
+// This code was generated by 'AltaSoft Choice.Generator'.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#nullable enable
+
+using TestNamespace;
+using AltaSoft.Choice;
+using System;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Xml;
+using System.Xml.Serialization;
+using System.Xml.Schema;
+
+namespace TestNamespace;
+
+#pragma warning disable CS8774 // Member must have a non-null value when exiting.
+#pragma warning disable CS0628 // New protected member declared in sealed type
+
+public sealed partial class XmlNamespaceChoice
+{
+ ///
+ /// Constructor for Serialization/Deserialization
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public XmlNamespaceChoice()
+ {
+ }
+
+ ///
+ /// Choice enum
+ ///
+ [JsonIgnore]
+ [XmlIgnore]
+ [ChoiceTypeProperty]
+ public ChoiceOf ChoiceType { get; private set; }
+
+ private string? _code;
+
+ ///
+ /// Specifies the code with namespace.
+ ///
+ [DisallowNull]
+ [XmlElement("Cd", Namespace = "urn:test:code")]
+ [ChoiceProperty]
+ public partial string? Code
+ {
+ get => _code;
+ set
+ {
+ _code = value ?? throw new InvalidOperationException("Choice value cannot be null");
+ _proprietary = null;
+ ChoiceType = ChoiceOf.Code;
+ }
+ }
+
+ private string? _proprietary;
+
+ ///
+ /// Specifies the proprietary value with namespace.
+ ///
+ [DisallowNull]
+ [XmlElement("Prtry", Namespace = "urn:test:proprietary")]
+ [ChoiceProperty]
+ public partial string? Proprietary
+ {
+ get => _proprietary;
+ set
+ {
+ _proprietary = value ?? throw new InvalidOperationException("Choice value cannot be null");
+ _code = null;
+ ChoiceType = ChoiceOf.Proprietary;
+ }
+ }
+
+
+ ///
+ /// Creates a new instance and sets its value using the specified .
+ ///
+ /// The value to assign to the created choice instance.
+ public static TestNamespace.XmlNamespaceChoice CreateAsCode(string value) => new () { Code = value };
+
+ ///
+ /// Creates a new instance and sets its value using the specified .
+ ///
+ /// The value to assign to the created choice instance.
+ public static TestNamespace.XmlNamespaceChoice CreateAsProprietary(string value) => new () { Proprietary = value };
+
+ ///
+ /// Applies the appropriate function based on the current choice type
+ ///
+ /// The return type of the provided match functions
+ /// Function to invoke if the choice is a value
+ /// Function to invoke if the choice is a value
+ public TResult Match(
+ Func matchCode,
+ Func matchProprietary)
+ {
+ return ChoiceType switch
+ {
+ ChoiceOf.Code => matchCode(Code!),
+ ChoiceOf.Proprietary => matchProprietary(Proprietary!),
+ _ => throw new InvalidOperationException($"Invalid ChoiceType. '{ChoiceType}'")
+ };
+ }
+
+ ///
+ /// Applies the appropriate Action based on the current choice type
+ ///
+ /// Action to invoke if the choice is a value
+ /// Action to invoke if the choice is a value
+ public void Switch(
+ Action matchCode,
+ Action matchProprietary)
+ {
+ switch (ChoiceType)
+ {
+ case ChoiceOf.Code:
+ matchCode(Code!);
+ return;
+
+ case ChoiceOf.Proprietary:
+ matchProprietary(Proprietary!);
+ return;
+
+ default:
+ throw new XmlException($"Invalid ChoiceType. '{ChoiceType}'");
+ }
+ }
+
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeCode() => ChoiceType == ChoiceOf.Code;
+
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeProprietary() => ChoiceType == ChoiceOf.Proprietary;
+
+ ///
+ /// Choice enumeration
+ ///
+ [XmlType("ChoiceOf.XmlNamespaceChoice")]
+ public enum ChoiceOf
+ {
+ ///
+ /// Specifies the code with namespace.
+ ///
+ Code,
+ ///
+ /// Specifies the proprietary value with namespace.
+ ///
+ Proprietary,
+ }
+}
diff --git a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldNotGenerateImplicitMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldNotGenerateImplicitMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs
index 4ed06af..35cb294 100644
--- a/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldNotGenerateImplicitMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs
+++ b/tests/AltaSoft.Choice.Generator.SnapshotTests/Snapshots/ChoiceGeneratorTest.ChoiceTypeShouldNotGenerateImplicitMethodsAndCompileCorrectly#Authorisation1Choice.g.verified.cs
@@ -164,14 +164,23 @@ public void Switch(
return value is null ? null : CreateAsProprietary(value.Value);
}
+ ///
+ /// Determines whether the property should be serialized.
+ ///
+ ///
+ /// true if is the active choice; otherwise, false.
+ ///
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeCode() => ChoiceType == ChoiceOf.Code;
+
///
/// Determines whether the property should be serialized.
///
///
- /// true if has a value; otherwise, false.
+ /// true if is the active choice; otherwise, false.
///
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
- public bool ShouldSerializeProprietary() => Proprietary.HasValue;
+ public bool ShouldSerializeProprietary() => ChoiceType == ChoiceOf.Proprietary;
///
/// Choice enumeration
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/AccountReport.cs b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/AccountReport.cs
new file mode 100644
index 0000000..bcd6ae8
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/AccountReport.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+
+///
+/// Account report document
+///
+[XmlRoot(ElementName = "AcctRpt", Namespace = "urn:test:account")]
+[Serializable]
+public sealed record AccountReport
+{
+ [XmlElement("RptId")]
+ [JsonPropertyName("rpt_id")]
+ public string? ReportId { get; set; }
+
+ [XmlElement("AcctId")]
+ [JsonPropertyName("acct_id")]
+ public string? AccountId { get; set; }
+
+ [XmlElement("Bal")]
+ [JsonPropertyName("bal")]
+ public decimal Balance { get; set; }
+
+ [XmlElement("BalDt")]
+ [JsonPropertyName("bal_dt")]
+ public DateTime BalanceDate { get; set; }
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/ApplicationHeader.cs b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/ApplicationHeader.cs
new file mode 100644
index 0000000..1b60518
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/ApplicationHeader.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+
+///
+/// Application header with custom XML namespace
+///
+[XmlRoot(ElementName = "AppHdr", Namespace = "urn:test:head")]
+[Serializable]
+public sealed record ApplicationHeader
+{
+ [XmlElement("BizMsgIdr")]
+ [JsonPropertyName("biz_msg_idr")]
+ public string? BusinessMessageIdentifier { get; set; }
+
+ [XmlElement("CreDtTm")]
+ [JsonPropertyName("cre_dt_tm")]
+ public DateTime CreationDateTime { get; set; }
+
+ [XmlElement("MsgDefIdr")]
+ [JsonPropertyName("msg_def_idr")]
+ public string? MessageDefinitionIdentifier { get; set; }
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/CustomerData.cs b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/CustomerData.cs
new file mode 100644
index 0000000..4bea297
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/CustomerData.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+
+///
+/// Customer data document
+///
+[XmlRoot(ElementName = "CstmrData", Namespace = "urn:test:customer")]
+[Serializable]
+public sealed record CustomerData
+{
+ [XmlElement("CstmrId")]
+ [JsonPropertyName("cstmr_id")]
+ public string? CustomerId { get; set; }
+
+ [XmlElement("Nm")]
+ [JsonPropertyName("nm")]
+ public string? Name { get; set; }
+
+ [XmlElement("Email")]
+ [JsonPropertyName("email")]
+ public string? Email { get; set; }
+
+ [XmlElement("PhoneNb")]
+ [JsonPropertyName("phone_nb")]
+ public string? PhoneNumber { get; set; }
+
+ [XmlElement("Ctry")]
+ [JsonPropertyName("ctry")]
+ public string? Country { get; set; }
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/MessageEnvelope.cs b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/MessageEnvelope.cs
new file mode 100644
index 0000000..8fc606d
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/MessageEnvelope.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+
+///
+/// Envelope containing header and business document choice
+///
+[XmlRoot(ElementName = "Envelope", Namespace = "urn:test:envelope")]
+[Serializable]
+[Choice.Choice]
+public sealed partial record MessageEnvelope
+{
+ ///
+ /// Application header
+ ///
+ [XmlElement("AppHdr", Namespace = "urn:test:head")]
+ [JsonPropertyName("app_hdr")]
+ public ApplicationHeader? Header { get; set; }
+
+ ///
+ /// Payment instruction document
+ ///
+ [Choice.XmlTag("PmtInstr", Namespace = "urn:test:payment")]
+ public partial PaymentInstruction? Payment { get; set; }
+
+ ///
+ /// Account report document
+ ///
+ [Choice.XmlTag("AcctRpt", Namespace = "urn:test:account")]
+ public partial AccountReport? Account { get; set; }
+
+ ///
+ /// Customer data document
+ ///
+ [Choice.XmlTag("CstmrData", Namespace = "urn:test:customer")]
+ public partial CustomerData? Customer { get; set; }
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/PaymentInstruction.cs b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/PaymentInstruction.cs
new file mode 100644
index 0000000..a00eb6d
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXml/PaymentInstruction.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Text.Json.Serialization;
+using System.Xml.Serialization;
+
+namespace AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+
+///
+/// Payment instruction document
+///
+[XmlRoot(ElementName = "PmtInstr", Namespace = "urn:test:payment")]
+[Serializable]
+public sealed record PaymentInstruction
+{
+ [XmlElement("InstrId")]
+ [JsonPropertyName("instr_id")]
+ public string? InstructionId { get; set; }
+
+ [XmlElement("Amt")]
+ [JsonPropertyName("amt")]
+ public decimal Amount { get; set; }
+
+ [XmlElement("Ccy")]
+ [JsonPropertyName("ccy")]
+ public string? Currency { get; set; }
+
+ [XmlElement("DbtrNm")]
+ [JsonPropertyName("dbtr_nm")]
+ public string? DebtorName { get; set; }
+
+ [XmlElement("CdtrNm")]
+ [JsonPropertyName("cdtr_nm")]
+ public string? CreditorName { get; set; }
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXmlNamespaceTests.cs b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXmlNamespaceTests.cs
new file mode 100644
index 0000000..71d8db4
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/AdvancedXmlNamespaceTests.cs
@@ -0,0 +1,564 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Serialization;
+using AltaSoft.Choice;
+using AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace AltaSoft.ChoiceGenerator.Tests;
+
+///
+/// Comprehensive tests for XML serialization with namespaces and Choice types in envelope structures.
+/// Tests verify that xsi:nil attributes are NOT generated and only the active choice is serialized.
+///
+public class AdvancedXmlNamespaceTests
+{
+ private readonly ITestOutputHelper _output;
+
+ private static readonly XmlWriterSettings s_xmlWriterSettings = new()
+ {
+ OmitXmlDeclaration = true,
+ Indent = true
+ };
+
+ public AdvancedXmlNamespaceTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ #region Payment Document Tests
+
+ [Fact]
+ public void XmlSerialization_Envelope_WithPaymentDocument_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+
+ MSG-12345
+ 2024-01-15T10:30:00
+ pacs.008.001.08
+
+
+ PMT-001
+ 1500.50
+ USD
+ John Doe
+ Jane Smith
+
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var envelope = (MessageEnvelope)serializer.Deserialize(reader)!;
+
+ // Validate Header
+ Assert.NotNull(envelope);
+ Assert.NotNull(envelope.Header);
+ Assert.Equal("MSG-12345", envelope.Header.BusinessMessageIdentifier);
+ Assert.Equal(new DateTime(2024, 1, 15, 10, 30, 0), envelope.Header.CreationDateTime);
+ Assert.Equal("pacs.008.001.08", envelope.Header.MessageDefinitionIdentifier);
+
+ // Validate Document Choice
+ Assert.Equal(MessageEnvelope.ChoiceOf.Payment, envelope.ChoiceType);
+ Assert.NotNull(envelope.Payment);
+ Assert.Null(envelope.Account);
+ Assert.Null(envelope.Customer);
+
+ // Validate Payment Details
+ var payment = envelope.Payment;
+ Assert.Equal("PMT-001", payment.InstructionId);
+ Assert.Equal(1500.50m, payment.Amount);
+ Assert.Equal("USD", payment.Currency);
+ Assert.Equal("John Doe", payment.DebtorName);
+ Assert.Equal("Jane Smith", payment.CreditorName);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_Envelope_CreatePaymentDocument_ShouldSerializeCorrectly()
+ {
+ var payment = new PaymentInstruction
+ {
+ InstructionId = "PMT-999",
+ Amount = 5000.00m,
+ Currency = "EUR",
+ DebtorName = "ACME Corp",
+ CreditorName = "Supplier Ltd"
+ };
+
+ var envelope = MessageEnvelope.CreateAsPayment(payment);
+ envelope.Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "MSG-999",
+ CreationDateTime = new DateTime(2024, 12, 25, 14, 0, 0),
+ MessageDefinitionIdentifier = "pacs.008.001.10"
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ _output.WriteLine("Generated Payment XML:");
+ _output.WriteLine(xml);
+
+ // Validate key elements presence
+ Assert.Contains("", xml);
+ Assert.Contains("MSG-999", xml);
+ Assert.Contains("", xml);
+ Assert.Contains("PMT-999", xml);
+ Assert.Contains("5000.00", xml);
+ Assert.Contains("EUR", xml);
+
+ // KEY ASSERTIONS: No xsi:nil and no other document types
+ Assert.DoesNotContain("xsi:nil=\"true\"", xml);
+ Assert.DoesNotContain("", xml);
+ Assert.Contains("PMT-VERIFY", xml);
+
+ // Verify Account and Customer are NOT present (no xsi:nil)
+ Assert.DoesNotContain("AcctRpt", xml);
+ Assert.DoesNotContain("CstmrData", xml);
+ Assert.DoesNotContain("xsi:nil", xml);
+ }
+
+ #endregion
+
+ #region Account Document Tests
+
+ [Fact]
+ public void XmlSerialization_Envelope_WithAccountDocument_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+
+ MSG-ACCT-001
+ 2024-06-15T09:00:00
+ camt.053.001.08
+
+
+ RPT-2024-001
+ ACC-123456
+ 25000.75
+ 2024-06-15T00:00:00
+
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var envelope = (MessageEnvelope)serializer.Deserialize(reader)!;
+
+ // Validate Header
+ Assert.NotNull(envelope);
+ Assert.NotNull(envelope.Header);
+ Assert.Equal("MSG-ACCT-001", envelope.Header.BusinessMessageIdentifier);
+ Assert.Equal("camt.053.001.08", envelope.Header.MessageDefinitionIdentifier);
+
+ // Validate Document Choice
+ Assert.Equal(MessageEnvelope.ChoiceOf.Account, envelope.ChoiceType);
+ Assert.Null(envelope.Payment);
+ Assert.NotNull(envelope.Account);
+ Assert.Null(envelope.Customer);
+
+ // Validate Account Details
+ var account = envelope.Account;
+ Assert.Equal("RPT-2024-001", account.ReportId);
+ Assert.Equal("ACC-123456", account.AccountId);
+ Assert.Equal(25000.75m, account.Balance);
+ Assert.Equal(new DateTime(2024, 6, 15), account.BalanceDate);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_Envelope_CreateAccountDocument_ShouldSerializeCorrectly()
+ {
+ var account = new AccountReport
+ {
+ ReportId = "RPT-TEST",
+ AccountId = "ACC-999",
+ Balance = 100000.00m,
+ BalanceDate = new DateTime(2024, 12, 31)
+ };
+
+ var envelope = MessageEnvelope.CreateAsAccount(account);
+ envelope.Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "MSG-TEST",
+ CreationDateTime = new DateTime(2024, 11, 1, 10, 30, 0),
+ MessageDefinitionIdentifier = "camt.053.001.10"
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ _output.WriteLine("Generated Account XML:");
+ _output.WriteLine(xml);
+
+ // Verify Account is present with correct namespace
+ Assert.Contains("", xml);
+ Assert.Contains("RPT-TEST", xml);
+ Assert.Contains("ACC-999", xml);
+ Assert.Contains("100000.00", xml);
+
+ // KEY ASSERTIONS: No xsi:nil and no other document types
+ Assert.DoesNotContain("xsi:nil=\"true\"", xml);
+ Assert.DoesNotContain("
+
+ MSG-CUST-001
+ 2024-03-20T14:30:00
+ acmt.001.001.06
+
+
+ CUST-789
+ Alice Johnson
+ alice.johnson@example.com
+ +1-555-0123
+ USA
+
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var envelope = (MessageEnvelope)serializer.Deserialize(reader)!;
+
+ // Validate Header
+ Assert.NotNull(envelope);
+ Assert.NotNull(envelope.Header);
+ Assert.Equal("MSG-CUST-001", envelope.Header.BusinessMessageIdentifier);
+ Assert.Equal("acmt.001.001.06", envelope.Header.MessageDefinitionIdentifier);
+
+ // Validate Document Choice
+ Assert.Equal(MessageEnvelope.ChoiceOf.Customer, envelope.ChoiceType);
+ Assert.Null(envelope.Payment);
+ Assert.Null(envelope.Account);
+ Assert.NotNull(envelope.Customer);
+
+ // Validate Customer Details
+ var customer = envelope.Customer;
+ Assert.Equal("CUST-789", customer.CustomerId);
+ Assert.Equal("Alice Johnson", customer.Name);
+ Assert.Equal("alice.johnson@example.com", customer.Email);
+ Assert.Equal("+1-555-0123", customer.PhoneNumber);
+ Assert.Equal("USA", customer.Country);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_Envelope_CreateCustomerDocument_ShouldSerializeCorrectly()
+ {
+ var customer = new CustomerData
+ {
+ CustomerId = "CUST-XYZ",
+ Name = "Bob Williams",
+ Email = "bob@company.com",
+ PhoneNumber = "+44-20-1234-5678",
+ Country = "UK"
+ };
+
+ var envelope = MessageEnvelope.CreateAsCustomer(customer);
+ envelope.Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "MSG-BOB",
+ CreationDateTime = new DateTime(2024, 11, 1, 8, 0, 0),
+ MessageDefinitionIdentifier = "acmt.002.001.06"
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ _output.WriteLine("Generated Customer XML:");
+ _output.WriteLine(xml);
+
+ // Verify Customer is present with correct namespace
+ Assert.Contains("", xml);
+ Assert.Contains("CUST-XYZ", xml);
+ Assert.Contains("Bob Williams", xml);
+ Assert.Contains("bob@company.com", xml);
+
+ // KEY ASSERTIONS: No xsi:nil and no other document types
+ Assert.DoesNotContain("xsi:nil=\"true\"", xml);
+ Assert.DoesNotContain(" $"Payment: {payment.InstructionId}",
+ account => $"Account: {account.AccountId}",
+ customer => $"Customer: {customer.Name}"
+ );
+
+ Assert.Equal("Payment: PMT-1", result);
+
+ var accountEnvelope = MessageEnvelope.CreateAsAccount(new AccountReport
+ {
+ AccountId = "ACC-123",
+ Balance = 1000m
+ });
+
+ result = accountEnvelope.Match(
+ payment => $"Payment: {payment.InstructionId}",
+ account => $"Account: {account.AccountId}",
+ customer => $"Customer: {customer.Name}"
+ );
+
+ Assert.Equal("Account: ACC-123", result);
+
+ var customerEnvelope = MessageEnvelope.CreateAsCustomer(new CustomerData
+ {
+ Name = "Test User"
+ });
+
+ result = customerEnvelope.Match(
+ payment => $"Payment: {payment.InstructionId}",
+ account => $"Account: {account.AccountId}",
+ customer => $"Customer: {customer.Name}"
+ );
+
+ Assert.Equal("Customer: Test User", result);
+ }
+
+ #endregion
+
+ #region Namespace Validation Tests
+
+ [Fact]
+ public void XmlSerialization_Envelope_AllNamespacesShouldBeCorrect()
+ {
+ var envelope = MessageEnvelope.CreateAsPayment(new PaymentInstruction
+ {
+ InstructionId = "PMT-NS",
+ Amount = 50m,
+ Currency = "GBP"
+ });
+ envelope.Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "NS-TEST",
+ CreationDateTime = new DateTime(2024, 1, 1),
+ MessageDefinitionIdentifier = "ns.test"
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ _output.WriteLine("Namespace validation XML:");
+ _output.WriteLine(xml);
+
+ // Validate all namespaces are present and correct
+ Assert.Contains("xmlns=\"urn:test:envelope\"", xml);
+ Assert.Contains("xmlns=\"urn:test:head\"", xml);
+ Assert.Contains("xmlns=\"urn:test:payment\"", xml);
+
+ // Validate namespace prefixes are not mixed
+ Assert.Contains("", xml);
+ Assert.Contains("", xml);
+
+ // Ensure only Payment namespace is present, not others
+ Assert.DoesNotContain("urn:test:account", xml);
+ Assert.DoesNotContain("urn:test:customer", xml);
+ }
+
+ [Fact]
+ public void XmlSerialization_Envelope_EachDocumentType_HasCorrectNamespace()
+ {
+ // Test Payment namespace
+ var paymentEnv = MessageEnvelope.CreateAsPayment(new PaymentInstruction { InstructionId = "P1" });
+ var xml1 = SerializeToString(paymentEnv);
+ _output.WriteLine("Payment namespace check:");
+ _output.WriteLine(xml1);
+ Assert.Contains("xmlns=\"urn:test:payment\"", xml1);
+ Assert.DoesNotContain("urn:test:account", xml1);
+ Assert.DoesNotContain("urn:test:customer", xml1);
+
+ // Test Account namespace
+ var accountEnv = MessageEnvelope.CreateAsAccount(new AccountReport { AccountId = "A1" });
+ var xml2 = SerializeToString(accountEnv);
+ _output.WriteLine("\nAccount namespace check:");
+ _output.WriteLine(xml2);
+ Assert.Contains("xmlns=\"urn:test:account\"", xml2);
+ Assert.DoesNotContain("urn:test:payment", xml2);
+ Assert.DoesNotContain("urn:test:customer", xml2);
+
+ // Test Customer namespace
+ var customerEnv = MessageEnvelope.CreateAsCustomer(new CustomerData { CustomerId = "C1" });
+ var xml3 = SerializeToString(customerEnv);
+ _output.WriteLine("\nCustomer namespace check:");
+ _output.WriteLine(xml3);
+ Assert.Contains("xmlns=\"urn:test:customer\"", xml3);
+ Assert.DoesNotContain("urn:test:payment", xml3);
+ Assert.DoesNotContain("urn:test:account", xml3);
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ private static string SerializeToString(MessageEnvelope envelope)
+ {
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+ return sw.ToString();
+ }
+
+ #endregion
+}
+
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/ChoiceGeneratorTests.cs b/tests/AltaSoft.ChoiceGenerator.Tests/ChoiceGeneratorTests.cs
index 2049c5c..f9b8601 100644
--- a/tests/AltaSoft.ChoiceGenerator.Tests/ChoiceGeneratorTests.cs
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/ChoiceGeneratorTests.cs
@@ -314,5 +314,71 @@ public void GenerateSinglePropertyChoice()
var switched = choice.Match(x => "matched");
Assert.Equal("matched", switched);
}
+
+ [Fact]
+ public void XmlNamespaceChoice_ShouldRoundTripWithNamespaces()
+ {
+ const string expectedXml = """
+
+ TEST-CODE
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(XmlNamespaceChoice));
+ using var reader = new StringReader(expectedXml);
+
+ var value = (XmlNamespaceChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(value);
+ Assert.Equal(XmlNamespaceChoice.ChoiceOf.Code, value.ChoiceType);
+ Assert.Equal("TEST-CODE", value.Code);
+ Assert.Null(value.Proprietary);
+
+ var settings = new XmlWriterSettings
+ {
+ OmitXmlDeclaration = true,
+ Indent = true
+ };
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, settings);
+
+ serializer.Serialize(writer, value, XmlNamespaceHelper.EmptyNamespace);
+
+ var serializedXml = sw.ToString();
+ Assert.Equal(expectedXml, serializedXml);
+ }
+
+ [Fact]
+ public void XmlNamespaceChoice_Proprietary_ShouldSerializeWithNamespace()
+ {
+ const string expectedXml = """
+
+ PROPRIETARY-DATA
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(XmlNamespaceChoice));
+ using var reader = new StringReader(expectedXml);
+
+ var value = (XmlNamespaceChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(value);
+ Assert.Equal(XmlNamespaceChoice.ChoiceOf.Proprietary, value.ChoiceType);
+ Assert.Null(value.Code);
+ Assert.Equal("PROPRIETARY-DATA", value.Proprietary);
+
+ var settings = new XmlWriterSettings
+ {
+ OmitXmlDeclaration = true,
+ Indent = true
+ };
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, settings);
+
+ serializer.Serialize(writer, value, XmlNamespaceHelper.EmptyNamespace);
+
+ var serializedXml = sw.ToString();
+ Assert.Equal(expectedXml, serializedXml);
+ }
}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/XmlChoiceSerializationTests.cs b/tests/AltaSoft.ChoiceGenerator.Tests/XmlChoiceSerializationTests.cs
new file mode 100644
index 0000000..6bc9cf7
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/XmlChoiceSerializationTests.cs
@@ -0,0 +1,427 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Serialization;
+using AltaSoft.Choice;
+using AltaSoft.ChoiceGenerator.Tests.OtherNamespace;
+using Xunit;
+
+namespace AltaSoft.ChoiceGenerator.Tests;
+
+///
+/// Comprehensive tests for XML serialization and deserialization of Choice types.
+///
+public class XmlChoiceSerializationTests
+{
+ private static readonly XmlWriterSettings s_xmlWriterSettings = new()
+ {
+ OmitXmlDeclaration = true,
+ Indent = true
+ };
+
+ #region TwoDifferentTypeChoice Tests
+
+ [Fact]
+ public void XmlSerialization_TwoDifferentTypeChoice_StringChoice_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ test value
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoDifferentTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoDifferentTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoDifferentTypeChoice.ChoiceOf.StringChoice, choice.ChoiceType);
+ Assert.Equal("test value", choice.StringChoice);
+ Assert.Null(choice.IntChoice);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_TwoDifferentTypeChoice_IntChoice_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ 42
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoDifferentTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoDifferentTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoDifferentTypeChoice.ChoiceOf.IntChoice, choice.ChoiceType);
+ Assert.Equal(42, choice.IntChoice);
+ Assert.Null(choice.StringChoice);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_TwoDifferentTypeChoice_CreateAsStringChoice_ShouldSerializeCorrectly()
+ {
+ var choice = TwoDifferentTypeChoice.CreateAsStringChoice("xml test");
+
+ var serializer = new XmlSerializer(typeof(TwoDifferentTypeChoice));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var xml = sw.ToString();
+ Assert.Contains("xml test", xml);
+ Assert.DoesNotContain("", xml);
+ }
+
+ #endregion
+
+ #region TwoSameTypeChoice Tests
+
+ [Fact]
+ public void XmlSerialization_TwoSameTypeChoice_StringChoiceOne_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ first
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoSameTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoSameTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoSameTypeChoice.ChoiceOf.StringChoiceOne, choice.ChoiceType);
+ Assert.Equal("first", choice.StringChoiceOne);
+ Assert.Null(choice.StringChoiceTwo);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_TwoSameTypeChoice_StringChoiceTwo_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ second
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoSameTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoSameTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoSameTypeChoice.ChoiceOf.StringChoiceTwo, choice.ChoiceType);
+ Assert.Null(choice.StringChoiceOne);
+ Assert.Equal("second", choice.StringChoiceTwo);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ #endregion
+
+ #region TwoValueTypeChoice Tests
+
+ [Fact]
+ public void XmlSerialization_TwoValueTypeChoice_Integer_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ 999
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoValueTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoValueTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoValueTypeChoice.ChoiceOf.Integer, choice.ChoiceType);
+ Assert.Equal(999, choice.Integer);
+ Assert.Null(choice.Code);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_TwoValueTypeChoice_EnumCode_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ One
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoValueTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoValueTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoValueTypeChoice.ChoiceOf.Code, choice.ChoiceType);
+ Assert.Equal(Authorisation1Code.One, choice.Code);
+ Assert.Null(choice.Integer);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ #endregion
+
+ #region Authorisation1Choice with XmlTag Tests
+
+ [Fact]
+ public void XmlSerialization_Authorisation1Choice_WithCustomXmlTag_Code_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ One
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(Authorisation1Choice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (Authorisation1Choice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(Authorisation1Choice.ChoiceOf.Code, choice.ChoiceType);
+ Assert.Equal(Authorisation1Code.One, choice.Code);
+ Assert.Null(choice.Proprietary);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_Authorisation1Choice_WithCustomXmlTag_Proprietary_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+
+ custom data
+
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(Authorisation1Choice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (Authorisation1Choice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(Authorisation1Choice.ChoiceOf.Proprietary, choice.ChoiceType);
+ Assert.Null(choice.Code);
+ Assert.NotNull(choice.Proprietary);
+ Assert.Equal("custom data", choice.Proprietary.Other);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ #endregion
+
+ #region DateTypeChoice with DateOnly Tests
+
+ [Fact]
+ public void XmlSerialization_DateTypeChoice_OnlyDate_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ 2024-12-25
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(DateTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (DateTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(DateTypeChoice.ChoiceOf.OnlyDate, choice.ChoiceType);
+ Assert.Equal(new DateOnly(2024, 12, 25), choice.OnlyDate);
+ Assert.Null(choice.DateTimeChoice);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_DateTypeChoice_DateTime_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ 2024-12-25T15:30:45
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(DateTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (DateTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(DateTypeChoice.ChoiceOf.DateTimeChoice, choice.ChoiceType);
+ Assert.Null(choice.OnlyDate);
+ Assert.Equal(new DateTime(2024, 12, 25, 15, 30, 45), choice.DateTimeChoice);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ #endregion
+
+ #region Edge Cases and Validation
+
+ [Fact]
+ public void XmlSerialization_EmptyStringValue_ShouldSerializeAndDeserialize()
+ {
+ const string expectedXml = """
+
+
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoDifferentTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoDifferentTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(TwoDifferentTypeChoice.ChoiceOf.StringChoice, choice.ChoiceType);
+ Assert.Equal(string.Empty, choice.StringChoice);
+ }
+
+ [Fact]
+ public void XmlSerialization_SpecialCharacters_ShouldBeEscaped()
+ {
+ const string expectedXml = """
+
+ <test> & "quotes"
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoDifferentTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoDifferentTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(" & \"quotes\"", choice.StringChoice);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ [Fact]
+ public void XmlSerialization_NegativeInteger_ShouldRoundTrip()
+ {
+ const string expectedXml = """
+
+ -42
+
+ """;
+
+ var serializer = new XmlSerializer(typeof(TwoDifferentTypeChoice));
+
+ // Deserialize
+ using var reader = new StringReader(expectedXml);
+ var choice = (TwoDifferentTypeChoice)serializer.Deserialize(reader)!;
+
+ Assert.NotNull(choice);
+ Assert.Equal(-42, choice.IntChoice);
+
+ // Serialize back
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, choice, XmlNamespaceHelper.EmptyNamespace);
+
+ var actualXml = sw.ToString();
+ Assert.Equal(expectedXml, actualXml);
+ }
+
+ #endregion
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/XmlNamespaceChoice.cs b/tests/AltaSoft.ChoiceGenerator.Tests/XmlNamespaceChoice.cs
new file mode 100644
index 0000000..66b33f3
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/XmlNamespaceChoice.cs
@@ -0,0 +1,13 @@
+using AltaSoft.Choice;
+
+namespace AltaSoft.ChoiceGenerator.Tests;
+
+[Choice]
+public sealed partial class XmlNamespaceChoice
+{
+ [XmlTag("Cd", Namespace = "urn:test:code")]
+ public partial string? Code { get; set; }
+
+ [XmlTag("Prtry", Namespace = "urn:test:proprietary")]
+ public partial string? Proprietary { get; set; }
+}
diff --git a/tests/AltaSoft.ChoiceGenerator.Tests/XmlNamespaceNilTests.cs b/tests/AltaSoft.ChoiceGenerator.Tests/XmlNamespaceNilTests.cs
new file mode 100644
index 0000000..a0d99c6
--- /dev/null
+++ b/tests/AltaSoft.ChoiceGenerator.Tests/XmlNamespaceNilTests.cs
@@ -0,0 +1,139 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Serialization;
+using AltaSoft.ChoiceGenerator.Tests.AdvancedXml;
+using Xunit;
+
+namespace AltaSoft.ChoiceGenerator.Tests;
+
+///
+/// Tests to verify that xsi:nil attributes are NOT generated for null Choice properties
+///
+public class XmlNamespaceNilTests
+{
+ private static readonly XmlWriterSettings s_xmlWriterSettings = new()
+ {
+ OmitXmlDeclaration = true,
+ Indent = true
+ };
+
+ [Fact]
+ public void MessageEnvelope_WithPayment_ShouldNotIncludeNilElements()
+ {
+ var payment = new PaymentInstruction
+ {
+ InstructionId = "PMT-999",
+ Amount = 5000.00m,
+ Currency = "EUR",
+ DebtorName = "ACME Corp",
+ CreditorName = "Supplier Ltd"
+ };
+
+ var envelope = new MessageEnvelope
+ {
+ Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "MSG-999",
+ CreationDateTime = new DateTime(2024, 12, 25, 14, 0, 0),
+ MessageDefinitionIdentifier = "pacs.008.001.10"
+ },
+ Payment = payment
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ // Verify Payment is present
+ Assert.Contains("", xml);
+ Assert.Contains("PMT-999", xml);
+
+ // Verify Account and Customer are NOT present (no xsi:nil)
+ Assert.DoesNotContain("AcctRpt", xml);
+ Assert.DoesNotContain("CstmrData", xml);
+ Assert.DoesNotContain("xsi:nil", xml);
+ }
+
+ [Fact]
+ public void MessageEnvelope_WithAccount_ShouldNotIncludeNilElements()
+ {
+ var account = new AccountReport
+ {
+ ReportId = "RPT-123",
+ AccountId = "ACC-456",
+ Balance = 10000.00m,
+ BalanceDate = new DateTime(2024, 12, 25)
+ };
+
+ var envelope = new MessageEnvelope
+ {
+ Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "MSG-ACCT",
+ CreationDateTime = new DateTime(2024, 12, 25, 10, 0, 0),
+ MessageDefinitionIdentifier = "camt.053"
+ },
+ Account = account
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ // Verify Account is present
+ Assert.Contains("", xml);
+ Assert.Contains("RPT-123", xml);
+
+ // Verify Payment and Customer are NOT present (no xsi:nil)
+ Assert.DoesNotContain("PmtInstr", xml);
+ Assert.DoesNotContain("CstmrData", xml);
+ Assert.DoesNotContain("xsi:nil", xml);
+ }
+
+ [Fact]
+ public void MessageEnvelope_WithCustomer_ShouldNotIncludeNilElements()
+ {
+ var customer = new CustomerData
+ {
+ CustomerId = "CUST-789",
+ Name = "John Doe",
+ Email = "john@example.com",
+ PhoneNumber = "+1-555-0100",
+ Country = "USA"
+ };
+
+ var envelope = new MessageEnvelope
+ {
+ Header = new ApplicationHeader
+ {
+ BusinessMessageIdentifier = "MSG-CUST",
+ CreationDateTime = new DateTime(2024, 12, 25, 15, 0, 0),
+ MessageDefinitionIdentifier = "acmt.001"
+ },
+ Customer = customer
+ };
+
+ var serializer = new XmlSerializer(typeof(MessageEnvelope));
+ using var sw = new StringWriter();
+ using var writer = XmlWriter.Create(sw, s_xmlWriterSettings);
+ serializer.Serialize(writer, envelope);
+
+ var xml = sw.ToString();
+
+ // Verify Customer is present
+ Assert.Contains("", xml);
+ Assert.Contains("CUST-789", xml);
+
+ // Verify Payment and Account are NOT present (no xsi:nil)
+ Assert.DoesNotContain("PmtInstr", xml);
+ Assert.DoesNotContain("AcctRpt", xml);
+ Assert.DoesNotContain("xsi:nil", xml);
+ }
+}