Skip to content

Fix XML flatten serialization: namespace loss, primitive formatting, document-root validity, and converter ordering#153

Merged
gimlichael merged 13 commits intomainfrom
copilot/sub-pr-152
Feb 27, 2026
Merged

Fix XML flatten serialization: namespace loss, primitive formatting, document-root validity, and converter ordering#153
gimlichael merged 13 commits intomainfrom
copilot/sub-pr-152

Conversation

Copy link
Contributor

Copilot AI commented Feb 27, 2026

Four correctness issues in the flattenItems XML serialization path identified during review.

Changes

XmlConverterDecoratorExtensions.cs

  • Namespace/prefix propagation: Dict-path container now uses w.WriteStartElement(q.Prefix, q.LocalName, q.Namespace) instead of q.LocalName only. Nested WriteObject root names carry namespace: new XmlQualifiedEntity(keyName, q.Namespace) (dict) and new XmlQualifiedEntity(q.Prefix, q.LocalName, q.Namespace) (list).
  • Primitive value formatting: Replaced WriteElementString(name, Convert.ToString(value, CultureInfo.InvariantCulture)) with WriteStartElement / WriteValue / WriteEndElement. XmlWriter.WriteValue produces schema-correct output (true/false for booleans, ISO 8601 round-trip for DateTime) consistent with the rest of the stack.
  • Document-root guard: Added w.WriteState == WriteState.Start check in the non-dictionary flatten branch. Without it, a bare list at document root emits multiple top-level elements (invalid XML). When at root, items are now wrapped in a container element using q's full qualified name.

XmlFormatterOptions.cs

  • Exhaustive converter removal: RefreshWithConverterDependencies previously removed only the first matching IEnumerable writer converter before inserting the flattened one. Changed to a loop that removes all matching converters, so FlattenCollectionItems = true takes effect regardless of how many IEnumerable-capable converters are registered ahead of it.
var converters = Decorator.Enclose(Settings.Converters);
while (true)
{
    var existing = converters.FirstOrDefaultWriterConverter(typeof(IEnumerable));
    if (existing == null) { break; }
    Settings.Converters.Remove(existing);
}
converters.AddEnumerableConverter(flattenItems: true);

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Base automatically changed from v10.4.0/xml-support-for-minimal-api to main February 27, 2026 01:43
Co-authored-by: gimlichael <8550919+gimlichael@users.noreply.github.com>
Copilot AI changed the title [WIP] Add XML support for minimal API Fix XML flatten serialization: namespace loss, primitive formatting, document-root validity, and converter ordering Feb 27, 2026
@gimlichael
Copy link
Member

@copilot - slighlty confused here. Since I have already merged #152 to main - this PR seems like double confect. Can you base it on main so I dont have 57 changes to look at? I need to only have your changes with diff from main.

Copy link
Contributor Author

Copilot AI commented Feb 27, 2026

@copilot - slighlty confused here. Since I have already merged #152 to main - this PR seems like double confect. Can you base it on main so I dont have 57 changes to look at? I need ...

The PR shows 57 files because the merge base between this branch and main is the old PR #151 merge commit (5f70ee9) — GitHub diffs from that ancestor, pulling in all of PR #152's files. The actual content diff between the branch and main tip is already only my 2 review-fix files.

To reduce the PR to just those 2 files, the branch needs to be rebased onto main — which requires a force push that I don't have access to. To fix it, you can run:

git fetch origin main
git rebase origin/main
git push --force-with-lease origin copilot/sub-pr-152

After that, the PR will show only the 2 changed files from my review feedback commit.

@sonarqubecloud
Copy link

@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 80.95238% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.74%. Comparing base (32af67f) to head (5ddc879).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...tion/Converters/XmlConverterDecoratorExtensions.cs 69.23% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #153      +/-   ##
==========================================
+ Coverage   80.66%   80.74%   +0.07%     
==========================================
  Files         600      601       +1     
  Lines       18932    19010      +78     
  Branches     1949     1964      +15     
==========================================
+ Hits        15272    15349      +77     
- Misses       3592     3593       +1     
  Partials       68       68              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gimlichael gimlichael marked this pull request as ready for review February 27, 2026 03:29
Copilot AI review requested due to automatic review settings February 27, 2026 03:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes correctness issues in Cuemon.Xml’s “flatten collection items” XML serialization by preserving namespaces/prefixes, producing schema-correct primitive text, ensuring well-formed document roots, and enforcing the intended enumerable-converter ordering when flattening is enabled.

Changes:

  • Preserve namespace/prefix when emitting flattened container and item elements, including nested WriteObject root names.
  • Switch primitive emission in the flatten path to XmlWriter.WriteValue and guard against invalid XML when flattening a root-level enumerable.
  • Ensure FlattenCollectionItems=true takes effect by removing all IEnumerable-writer converters before inserting the flattened enumerable converter.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Cuemon.Xml/Serialization/Formatters/XmlFormatterOptions.cs Removes all IEnumerable writer converters when flattening is enabled, then re-adds the flattened enumerable converter to enforce effective ordering.
src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs Fixes flatten-path XML emission: namespace/prefix propagation, schema-correct primitive formatting via WriteValue, and document-root wrapping for root enumerables.

Comment on lines +141 to +159
var isDocumentRoot = w.WriteState == WriteState.Start;
if (isDocumentRoot) { w.WriteStartElement(q.Prefix, q.LocalName, q.Namespace); }
var qe = new XmlQualifiedEntity(q.Prefix, q.LocalName, q.Namespace);
foreach (var item in o)
{
if (item == null) { continue; }
var itemType = item.GetType();
if (Decorator.Enclose(itemType).IsComplex())
{
var localName = q.LocalName;
Decorator.Enclose(w).WriteObject(item, itemType, opts => opts.Settings.RootName = new XmlQualifiedEntity(localName));
Decorator.Enclose(w).WriteObject(item, itemType, opts => opts.Settings.RootName = qe);
}
else
{
w.WriteElementString(q.LocalName, Convert.ToString(item, CultureInfo.InvariantCulture));
w.WriteStartElement(q.Prefix, q.LocalName, q.Namespace);
w.WriteValue(item);
w.WriteEndElement();
}
}
if (isDocumentRoot) { w.WriteEndElement(); }
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated flatten-items writer logic (namespace/prefix propagation, WriteValue formatting, and document-root wrapping) is not currently covered by unit tests. Please add tests that exercise: (1) FlattenCollectionItems=true with a root IEnumerable where Settings.RootName is set (ensuring the output is well-formed XML with a single document element), (2) primitive formatting (e.g., bool emits "true/false"), and (3) namespace/prefix preservation on the emitted elements.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +139
var converters = Decorator.Enclose(Settings.Converters);
while (true)
{
var existing = converters.FirstOrDefaultWriterConverter(typeof(IEnumerable));
if (existing == null) { break; }
Settings.Converters.Remove(existing);
}
converters.AddEnumerableConverter(flattenItems: true);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefreshWithConverterDependencies now removes all writer converters that can convert IEnumerable before inserting the flattened enumerable converter. Please add a unit test that registers multiple IEnumerable-capable converters ahead of the default one and verifies that when FlattenCollectionItems=true, serialization actually uses the flattened converter (and that all prior IEnumerable writer converters are removed as intended).

Copilot uses AI. Check for mistakes.
@gimlichael gimlichael merged commit 4f92de6 into main Feb 27, 2026
310 checks passed
@gimlichael gimlichael deleted the copilot/sub-pr-152 branch February 27, 2026 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants