Skip to content

[API Proposal]: Add configurable list separator to XmlTextAttribute and XmlAttributeAttribute #129001

@StephenMolloy

Description

@StephenMolloy

Background and motivation

XmlSerializer currently has inconsistent and non-configurable behavior when serializing array-like members as XML text versus XML attributes.

For [XmlAttribute] string[], XmlSerializer serializes values as a whitespace-separated list:

<element values="a b c d" />

For [XmlText] string[], XmlSerializer serializes values by concatenating them with no separator:

<element>abcd</element>

This behavior is longstanding and changing the default would be breaking. However, it makes it difficult to model XML Schema xs:list-style element text values, where users often expect list items to be serialized with a separator, commonly a space.

Today, users must work around this by hiding the actual collection from XmlSerializer and exposing a shadow string property that manually joins and splits the values:

[XmlIgnore]
public string[] Items { get; set; }

[XmlText]
public string ItemsSerialized
{
    get => string.Join(" ", Items);
    set => Items = value?.Split(' ') ?? Array.Empty<string>();
}

This workaround is repetitive, error-prone, and requires users to change their object model purely for serialization purposes.

This proposal adds an opt-in separator property to the existing XML serialization attributes so users can preserve existing behavior by default while configuring list serialization when needed.

Related issue: dotnet/runtime#115837
Prototype PR: dotnet/runtime#126767

API Proposal

namespace System.Xml.Serialization;

public class XmlTextAttribute : Attribute
{
    public char Separator { get; set; }
}

public class XmlAttributeAttribute : Attribute
{
    public char Separator { get; set; }
}

API Usage

XmlTextAttribute.Separator

XmlTextAttribute.Separator controls how array-like XML text values are serialized and deserialized.

The default value is '\0'.

When Separator == '\0', existing behavior is preserved:

public class Example
{
    [XmlText]
    public string[] Items { get; set; }
}

Serializing:

new Example { Items = new[] { "a", "b", "c" } }

continues to produce:

<Example>abc</Example>

Deserialization likewise preserves existing behavior: the full text content is treated as one string value.

When Separator is set to a non-default character, adjacent string values are separated using that character during serialization, and text content is split on that character during deserialization:

public class Example
{
    [XmlText(Separator = ' ')]
    public string[] Items { get; set; }
}

Serializing:

new Example { Items = new[] { "a", "b", "c" } }

produces:

<Example>a b c</Example>

Deserializing:

<Example>a b c</Example>

produces:

Items = new[] { "a", "b", "c" };

A non-whitespace separator can also be used:

public class Example
{
    [XmlText(Separator = ',')]
    public string[] Items { get; set; }
}
<Example>a,b,c</Example>

XmlAttributeAttribute.Separator

XmlAttributeAttribute.Separator controls how array-like XML attribute values are serialized and deserialized.

The default value is '\0'.

When Separator == '\0', existing behavior is preserved:

public class Example
{
    [XmlAttribute]
    public string[] Items { get; set; }
}

Serializing:

new Example { Items = new[] { "a", "b", "c" } }

continues to produce:

<Example Items="a b c" />

Deserialization continues to use the existing whitespace-splitting behavior.

When Separator is set to a non-default character, that character is used instead:

public class Example
{
    [XmlAttribute(Separator = ',')]
    public string[] Items { get; set; }
}

Serializing:

new Example { Items = new[] { "a", "b", "c" } }

produces:

<Example Items="a,b,c" />

Deserializing:

<Example Items="a,b,c" />

produces:

Items = new[] { "a", "b", "c" };

Alternative Designs

No response

Risks

Values containing the separator

This API does not introduce escaping for separator characters inside individual list values.

For example:

[XmlText(Separator = ' ')]
public string[] Values { get; set; } = new[] { "a b", "c d" };

would serialize as:

<Example>a b c d</Example>

and deserialize as:

new[] { "a", "b", "c", "d" }

rather than preserving the original two values.

This is consistent with delimiter-based list serialization generally: users must choose a separator that cannot appear in individual values, or use a different serialization shape.

This limitation should be documented.

Different default semantics between the two attributes

The default '\0' value preserves existing behavior, but that means the effective defaults differ:

  • [XmlText] with no separator continues to concatenate values.
  • [XmlAttribute] with no separator continues to use existing whitespace-separated behavior.

This asymmetry already exists today. The proposed API preserves it for compatibility.

Exact-character splitting versus existing whitespace splitting

For [XmlAttribute], existing deserialization splits on whitespace. With a custom separator, deserialization would split on the exact configured character.

For example:

[XmlAttribute(Separator = ' ')]
public string[] Values { get; set; }

would split on the literal space character, not on all whitespace characters.

This is a behavior distinction from the default Separator == '\0' path and should be called out in documentation.

Metadata

Metadata

Assignees

Labels

api-ready-for-reviewAPI is ready for review, it is NOT ready for implementationapi-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-SerializationuntriagedNew issue has not been triaged by the area owner

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions