Skip to content

Add direct ISourceNode.ToPoco() implementation#3472

Open
cknaap wants to merge 7 commits intodevelopfrom
feature/3471-sourcenode-to-poconode
Open

Add direct ISourceNode.ToPoco() implementation#3472
cknaap wants to merge 7 commits intodevelopfrom
feature/3471-sourcenode-to-poconode

Conversation

@cknaap
Copy link
Copy Markdown
Member

@cknaap cknaap commented Mar 24, 2026

Description

This pull request introduces a direct conversion from ISourceNode to Poco. For this, it refactors the implementation of the NewPocoBuilder for deserializing FHIR resources from ISourceNode (new) and ITypedElement (existing) trees, and updates the deserialization benchmarks to use and compare this new approach. The changes improve the flexibility, maintainability, and correctness of POCO construction from various node types, and add more comprehensive benchmarks for deserialization strategies.

Core NewPocoBuilder Implementation:

  • Added a new partial, internal implementation of NewPocoBuilder, split into NewPocoBuilder.Shared.cs, NewPocoBuilder.SourceNode.cs, and NewPocoBuilder.TypedElement.cs, providing robust logic for constructing POCOs from both ISourceNode and ITypedElement trees, including dynamic type resolution, property assignment, and primitive normalization.
  • Fixed a bug in classMappingForElement (NewPocoBuilder.SourceNode.cs) where propertyMapping.ImplementingType was used to look up the class mapping. For properties with an abstract ImplementingType but a single concrete FhirType (e.g. Signature.who in R4, typed as DataType but always holding a Reference), the code incorrectly fell through to DynamicDataType. The fix uses propertyMapping.GetInstantiableType() instead, which resolves to the single concrete type when only one is allowed.

Refactoring and API Improvements:

  • Changed NewPocoBuilder from a single file to a partial class, encapsulating logic and making the constructor public for broader usability. Also removed unused usings and cleaned up the main file.

Benchmark Enhancements:

  • Updated DeserializationBenchmark.cs to add new benchmark methods for the new POCO builder, comparing direct and bridge deserialization for both JSON and XML. Removed the XmlReader class field (which caused a name collision with System.Xml.XmlReader) and replaced it with a local using var declaration inside XmlDictionaryDeserializer(), also ensuring proper disposal.

General Code Quality:

  • Improved naming conventions for internal variables and removed unused imports for clarity and maintainability.

These changes collectively modernize and enhance the deserialization pipeline, making it easier to extend and reason about, and providing a strong foundation for future improvements.

Related issues

Testing

Added unit tests and benchmark tests. The fix for Signature.who round-trip deserialization is covered by the existing RoundtripSignature.WorksWithTypedElementSerializers test (16,233 R4 serialization tests pass).

FirelyTeam Checklist

  • Update the title of the PR to be succinct and less than 50 characters
  • Mark the PR with the label breaking change when this PR introduces breaking changes

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copy link
Copy Markdown
Contributor

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

This PR adds a direct ISourceNode -> POCO/PocoNode conversion path by extending NewPocoBuilder to traverse ISourceNode trees (in addition to ITypedElement) and updates tests/benchmarks to validate and measure the new approach.

Changes:

  • Refactors NewPocoBuilder into shared + typed-element + source-node partials, and adds BuildFrom(ISourceNode, ...).
  • Updates ISourceNode conversion extensions to use the new direct builder path and adds ISourceNode.ToPocoNode(...).
  • Adds unit tests for direct ISourceNode conversion behavior and expands benchmarks to compare direct vs bridged deserialization.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Hl7.Fhir.Support.Poco.Tests/Model/SourceNodeToPocoTests.cs Adds coverage for direct ISourceNode to POCO/PocoNode conversion, overflow behavior, and annotation preservation.
src/Hl7.Fhir.Shims.STU3AndUp/Serialization/JsonSerializationOptionsExtensions.cs Cleanup of nullable directive/footer formatting.
src/Hl7.Fhir.Shims.STU3AndUp/ElementModel/VersionedConversionExtensions.cs Adds ISourceNode-scoped extension members for ToPoco() / ToPocoNode().
src/Hl7.Fhir.Base/Serialization/engine/ElementModelSerializationEngine.cs Switches to explicit LegacyPocoBuilder invocation to deserialize ISourceNode into a resource.
src/Hl7.Fhir.Base/ElementModel/SourceNodeExtensions.Conversions.cs Implements direct ISourceNode.ToPoco(...) via NewPocoBuilder and adds ISourceNode.ToPocoNode(...).
src/Hl7.Fhir.Base/ElementModel/NewPocoBuilder.cs Splits core builder into a partial class and adds BuildFrom(ISourceNode, ...) entry point.
src/Hl7.Fhir.Base/ElementModel/NewPocoBuilder.TypedElement.cs Moves ITypedElement traversal/deserialization logic into a dedicated partial.
src/Hl7.Fhir.Base/ElementModel/NewPocoBuilder.SourceNode.cs Adds direct ISourceNode traversal/deserialization logic including choice/type handling.
src/Hl7.Fhir.Base/ElementModel/NewPocoBuilder.Shared.cs Extracts shared helpers (instance creation, list assignment, primitive normalization, enum validation).
src/Benchmarks/DeserializationBenchmark.cs Adds direct-source-node benchmark scenarios and restructures setup/state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Benchmarks/DeserializationBenchmark.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 24, 2026

@cknaap I've opened a new pull request, #3473, to work on those changes. Once the pull request is ready, I'll request review from you.

@cknaap
Copy link
Copy Markdown
Member Author

cknaap commented Mar 25, 2026

@copilot : The build pipeline failed with a test failure. Can you try to fix that? The failure is:
Test method Hl7.Fhir.Serialization.Tests.RoundtripSignature.WorksWithTypedElementSerializers threw exception:
Xunit.Sdk.XunitException: Expected boolean to be True, but found False.

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

@copilot : The build pipeline failed with a test failure. Can you try to fix that? The failure is:
Test method Hl7.Fhir.Serialization.Tests.RoundtripSignature.WorksWithTypedElementSerializers threw e...

Fixed in 4affcb4. The root cause was in classMappingForElement in NewPocoBuilder.SourceNode.cs — it was using propertyMapping.ImplementingType to look up the class mapping, but for Signature.who in R4 that type is the abstract DataType. Since no type annotation is available on the raw ISourceNode, the code fell through to DynamicDataType instead of the concrete Reference type.

The fix is to use propertyMapping.GetInstantiableType() instead, which correctly returns the single concrete FhirType (ResourceReference) when ImplementingType is abstract and only one type is allowed. All 16,233 R4 serialization tests now pass.

@cknaap cknaap requested review from a team, Kasdejong, andrzejskowronski and ewoutkramer and removed request for a team and Kasdejong April 9, 2026 08:28
@alexzautke alexzautke requested review from a team and removed request for andrzejskowronski and ewoutkramer April 9, 2026 08:30
{
try
{
return (Resource)deserializer().ToPoco(inspector, null, pocoSettings);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the previous one would do the checks for the type returned and skip building if PocoNode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The readFromElement had way more comments in the original version documenting why certain choices are important for compatibility, would be a shame to lose them.

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.

4 participants