Skip to content

Breaking changes in 6.0

Ewout Kramer edited this page Mar 4, 2025 · 68 revisions

Breaking changes

  • Removed support for binary serialization.
  • Removed the IDeepCopyable interface, which was previously implemented on our POCOs. Its methods are now accessible via extensions.
  • Removed IsResource from the FhirTypeAttribute. One can just check against the Resource subtype instead.
  • Renamed the IsNestedType parameter on FhirTypeAttribute to IsBackboneType to align with common FHIR jargon.
  • Removed the BackboneTypeAttribute class. One can use IsBackboneType (above), the type's name (that was in this attribute) is now the ClassMapping.Name.
  • Removed the DefinitionPath property of ClassMapping, since it is the same as the ClassMapping.Name for backbone types.
  • POCO's no longer implement IDictionary<string,object>. Although this sounded like a good idea, it did break frameworks and made the debuggers display resoures as dictionaries. The interface is also not enough to base serializers or validators on, so it's use is limited. Therefore, we decided to remove it. You can now use EnumerateElements() instead, or functions like TryGetValue() and SetValue and the indexer ([]).
  • Since we now have a standard way to dynamically navigate a POCO, we removed Base.Children and Base.NamedChildren properties, since these have overlapping functionalities. Note that there is an extension method Children() (obsolete) that you can use for backwards-compatibility.
  • The type of ElementDefinition.Constraint.RequirementsElement, ElementDefinition.Binding.DescriptionElement, ElementDefinition.Mapping.CommentElement and CapabilityStatement.Implementation.DescriptionElement have changed from Markdown to PrimitiveType. Set this property to Markdown to get the original behaviour. This change allows you to use the correct type depending on the version of FHIR you are writing against (which matters in e.g. FhirPath expressions that match against type).
  • The type of Attachment.SizeElement has changed from Integer64 to PrimitiveType. Set this property to Integer64 to get the original behaviour, but you should now assign the correct type (i.e. UnsignedInteger before R5) depending on the version of FHIR you are writing against.
  • The type of Bundle.Link.RelationElement has changed from FhirString to PrimitiveType. Set this property to Integer64 to get the original behaviour, but you can now use the correct type depending on the version of FHIR you are writing against.
  • The type of Meta.ProfileElement has changed from Canonical to Code. Set this property to Integer64 to get the original behaviour, but you can now use the correct type depending on the version of FHIR you are writing against.
  • Each of the above elements no have multiple helper properties (e.g. Attachment.Size and Attachment.SizeUnsignedInteger) to allow you to use "simple assignments" of primitives.
  • IAnnotatable now inherits IAnnotated, so implementers of the former will have to implement the latter. Which makes sense, since objects that can be annotated should provide support for reading those annotations too.
  • Resource.HasVersionId has been removed as part of a clean up of the public interface of Resource. Just check Resource.VersionId for null.
  • Resource.SyncLock has been removed, as this is not a responsibility for Resource, and lock objects will change in .NET 9.
  • The IDeepComparable interface has disappeared, the Matches() and IsExactly() functions now simply take a Base as parameter. Also, it is now possible to write new custom comparison operations using the new CompareChildren() function, given a custom implementation of IEqualityComparer<T>.
  • The [Bindable] attribute has been removed in favor of the more useful ICoded. So, if you want to determine whether a datatype can be bound to, you can check for ICoded, and then call its members to get to a coded representation of that datatype. This is also true for Resources, though that is normally only used in the context of CQL.
  • ISystemAndCode has been removed from Code<T>, it now implements the equivalent (but more general) ICoded, like all datatypes.
  • FhirDateTime.TryToDateTime has been renamed to FhirDateTime.TryToSystemDateTime to make it clear what the output is.
  • Time.TryToTime has been renamed to Time.TryToSystemTime to make it clear what the output is.
  • Date.TryToDate has been renamed to Date.TryToSystemDate to make it clear what the output is.
  • Any.TryParse() turned out to be both confusing in its use and a bit too specific to be useful generally and has been removed. We introduced Any.TryParseToAny() to replace it.
  • ICqlConvertible did follow Microsoft's example, by listing tons of TryConvertToXXX() methods, but it turned out that it was hard to use this way. It has been replaced by Any.TryConvertTo(), which has a parameter with the desired target type.
  • Many methods in the ElementModel.Types namespace used our own Result<T>, which is common in functional languages, but not idiomatic for C# developers, so we replaced the functions returning Result<T> with "normal" TryXXXXX methods (e.g. TryConvertTo()) returning bool and an out parameter. We will revisit when Microsoft introduces discriminated unions and standard types for this purpose.
  • For some System/CQL types, we previously had parameterless constructors that assumed a default (e.g. the empty string for Types.String) - this was unexpected and they have been removed. These types will require you to have a value for the string (or boolean, etc).
  • We removed the operators for Quantity multiplication, division, addition and substraction, since these implementations were wrong or at least simplistic. These operations need to be done using a real unit and precision aware library, which we might add later. This also means FhirPath support for these operations has been removed for now.
  • FHIR Time, Instant, Date and FhirDateTime POCO comparison operators have been removed, they just forwarded the comparison to the equivalent CQL Date type. Instead, call ConvertToSystemDate() and invoke the comparisons on the CQL/system Date.
  • The ElementValue class was no longer used by the SDK and has been removed.
  • The conversion operator from .NET primitive types (like boolean and string) to CQL/System primitives have been made explicit to avoid surprises.
  • Only POCO's that can be validated are implementing IValidatableObject, whereas previously, all resources had a default implementation (mostly returning []).
  • Most extension methods on the data models (most notably, serialization and conversions) have been moved to more central locations. For some extension methods, this means their namespace has changed.
    • All such extensions on ITypedElement/ISourceNode-based classes have been moved to the Hl7.Fhir.ElementModel namespace.
    • All such extensions on POCO-based classes have been moved to the Hl7.Fhir.Model namespace.
  • The extension methods for conversions between Base and ITypedElement for a specific FHIR version have been unified under Hl7.Fhir.ElementModel.VersionedConversionExtensions.
  • The class FilterPredicateExtensions has been renamed to CodedExceptionFilters to better match the purpose of the class.

Breaking changes to the parsers

  • Since the POCO's ObjectValue now captures strings for base64, long and instant, the parsers no longer need to validate and parse the literals, but instead will put them directly into the POCO's. This means that the validation for these types (and thus, now all types) have been moved to the POCO's. The errors from FhirXmlException and FhirJsonException that were raised by these validation (INCORRECT_BASE64_DATA, _CANNOT_BE_PARSED, _INCORRECT_FORMAT, _LITERAL_INVALID) have been replaced with errors from the CodedValidationException (INVALID_BASE64_VALUE_CODE, INCORRECT_LITERAL_VALUE_TYPE_CODE, LITERAL_INVALID_CODE).
  • FhirXmlPocoDeserializerSettings.DisableBase64Decoding and FhirJsonConverterOptions.DisableBase64Decoding have been removed, since the parser will no longer decode base64 data at all (this is now done in the POCO, deferred until the moment it is needed).
  • The parser will set the values for properties and ObjectValue before calling the methods provided in IDeserializationValidator. These methods can therefore no longer mutate their arguments, the arguments are no longer "by ref". ValidateProperty is also no longer called for the Value property of primitives, since the validation of these values are deferred to the POCO's themselves.
  • DataAnnotationDeserialzationValidator.NarrativeValidation has been moved to the FhirJsonConverterOptions and the FhirXmlPocoDeserializerSettings. Not only was this option too hard to find, but also logically it is an option of validation, not an option of the validator. In other words, all implementations of IDeserializationValidator should deal with this option, not just this specific implementation.
  • BaseFhirParser has gone, and so are its parse methods to "parse" an ITypedElement or ISourceNode to a POCO. Use ToPoco<T>() instead on the ITypedElement or ISourceNode. The same applies to FhirXmlParser and FhirJsonParser.
  • The serializers do not have constructors taking an Assembly anymore, they will use ModelInspector instead (like all other version-agnostic parts of the SDK). Replace the parameter in these calls with ModelInspector.ForType(typeof(Patient)) or ModelInspector.ForAssembly() instead.
  • FhirXmlPocoDeserializerSettings has been changed to a record instead of a class, to align with our best practices for settings classes used in other parts of the SDK.
  • PrimitiveParseHandler was not used anymore by the SDK and has been removed.
  • The enum DeserializerModes has been renamed to DeserializationMode.
  • Exception filtering has moved from the engines to the parsers, so FhirSerializationEngineFactory.Custom() does not accept a filter parameter anymore, you will have to configure the xml/json settings you are passing in to the call to Custom().
  • ParserSettings.TruncateDateTimeToDate has been removed. It was a fix for a historic bug, which should not appear anymore. This functionality can be reproduced, if necessary, by exploiting the Validator property in FhirJsonConverterOptions/FhirXmlPocoDeserializerSettings. This will allow you to get a callback when a parsed object is validated. At this moment, you can manipulate the data as well, e.g. when validating a FhirDateTime.
  • ParserSettings.ExceptionHandler has been removed. Since the FhirXmlParser/FhirJsonParser now use the newer technology used by FhirXml/JsonPocoDeserializer, and not the ITypedElement-based parsers anymore, there are no more callbacks on errors. Instead, these new parsers will allow you to get a full list of exceptions after parsing is done.

Breaking changes to the FhirPath engine

The FhirPath engine has been refactored to work against PocoNode instead of ITypedElement. As such, all the internal FP signatures have had their argument/return types changed from ITE to PocoNode. Note that PocoNode implements ITypedElement, so in most cases this should be compile-compatible.

  • Changed many of the signature-declared types in internal FhirPath engine methods from ITypedElement to PocoNode
  • Removed methods which became obsolete due to this change. Most notably ToFhirPathResolver

Breaking changes to the serializers

All the settings classes for serializers (FhirJson/XmlSerializationSettings, SerializerSettings, FhirJsonPocoSerializerSettings) have been taken out:

  • Pretty printing is not a configuration, but has returned as a parameter to serialization functions that produce a string or bytearray.
  • There is no longer a way to throw an exception if the serializer encounters an unknown element: this kind of "validation" is part of the parser and validator, and felt rather out of place in the serializer (which, by design, does not do any other kind of validation).
  • Summary filters can be passed as arguments to the serialization functions, since they are not a constant configuration option for a serializer, as the kind of summarization may vary from call to call to the serializer. This also meant removing the IncludeMandatoryElementsInSummary setting, as this should now be controlled by constructing a summary filter using SerializationFilter.ForSummary(), which has a parameter to control this setting. This filter is then passed as an argument to the serializer calls (or as an option to ForFhir()).
  • The ability to control trimming whitespace in XML values has been removed since the FHIR specification demands whitespace is always trimmed.
  • Most async versions of the serializers have been removed since we are using XmlWriter for Xml and have switched from Newtonsoft to System.Text.Json.Utf8JsonWriter internally, all of which have no support for async.
  • When configuring the System.Text.Json stack with ForFhir(), you no longer have to pass in separate serializer and deserializer option structures, they have both been combined into a single FhirJsonConverterOptions.
  • The BaseFhirPocoXml/JsonSerializer took a FhirRelease as a constructor argument, while all other multi-version classes (like BaseFhirClient) took a ModelInspector. We have made this consistent by letting the serializers have a ModelInspector argument.
  • The abstract base for all serializers, BaseFhirSerializer was not truely a useful baseclass for anything, it just contained some protected re-usable functions, that we have turned into (internal) extension methods. As part of cleaning up the deserializers and serializers, this class has been removed.
  • The new serializers implement stricter rules on encoding of special characters. While not a breaking change per se (the json is semantically equivalent after all), a character-by-character comparison of the outputs will not necessarily be the same (e.g. the + in timezones is being escaped). This is because we are using System.Text.Json's default encoder, not UnsafeRelaxedJsonEscaping.
  • Since we have combined the existing APIs of FhirXml/JsonSerializer and FhirXml/JsonPocoSerializer, we had to make the SummaryType parameter mandatory, to avoid ambiguous overloads. Similarly, we have renamed the optional root parameter for the XML serialization functions to rootName for consistency.
  • The serializer inheritance structure has been simplified, since there is no longer an "old" and "new" serializer, and now looks like this:
classDiagram
    BaseFhirXmlSerializer <|-- FhirXmlSerializer
    BaseFhirXmlSerializer <|-- BaseFhirXmlPocoSerializer
    BaseFhirXmlPocoSerializer <|-- FhirXmlPocoSerializer

    class BaseFhirXmlSerializer{
    }
    class FhirXmlSerializer{
    }
    class BaseFhirXmlPocoSerializer{
       << Obsolete >>
    }
    class FhirXmlPocoSerializer{
       << Obsolete >>
    }
Loading

Note that the "new" serializers are marked 'Obsolete', this is because their functionality has been moved to what used to be the old serializers (BaseFhirXmlSerializer/FhirXmlSerializer), so the obsolete messages will indicate that a rename is in place.

Removed obsolete members

  • ArtifactSummaryGenerator.Default has been removed, the constructor should be used instead.
  • Most synchronous versions of async extension methods on FhirClient have been removed. Those methods were just wrapping async calls, so the recommendation is to use Async version instead.
  • FhirClient.ExpandValueSet and FhirClient.ValidateCode have been marked as obsolete, async versions are recommended instead.
  • ClassMapping.IsNestedType has been removed, ClassMapping.IsBackboneType should be used to align with FHIR jargon.
  • VERSION_CONTENT_HEADER has been removed, VERSION_CONTENT_HEADER_NAME should be used instead.
  • ElementDefinition.Name has been removed, ElementDefinition.SliceName should be used instead.
  • FhirClientSettings.PreferredReturn has been removed, FhirClientSettings.ReturnPreference and/or FhirClientSettings.UseAsync instead.
  • FhirClientSettings.CompressRequestBody has been removed, FhirClientSettings.RequestBodyCompressionMethod instead.
  • FhirXmlException.ENCOUNTERED_DTP_REFERENCES_CODE has been removed as it had a typo, FhirXmlException.ENCOUNTERED_DTD_REFERENCES_CODE should be used instead.
  • Multiple constructors for FhirJsonPocoDeserializers have been removed, the default constructor should be used if a single version of FHIR is user, otherwise BaseFhirJsonPocoDeserializer should be used instead.
  • Multiple constructors for FhirXmlPocoDeserializer have been removed, the default constructor should be used if a single version of FHIR is user, otherwise BaseFhirXmlPocoDeserializer should be used instead.
  • HttpUtil.Prefer has been removed, HttpUtil.ReturnPreference should be used instead.
  • Lexer.Quantity has been removed as it's no longer used by FhirPath parser.
  • SearchParamDefinition.ComposityParams has been removed, SearchParamDefinition.Component should be used instead.
  • Element.RemoveAllConstrainedByDiffExtensions and IEnumerable<T>.RemoveAllConstrainedByDiffExtensions extensions have been removed, RemoveAllNonInheritableExtensions should be used instead.
  • TransactionBuilder.Create,TransactionBuilder.Update,TransactionBuilder.Patch,TransactionBuilder.Delete have been removed, the ConditionalCreate/ConditionalUpdate/ConditionalPatch/ConditionalDeleteSingle should be used instead.
  • ValidateEnumCodeAttribute has been removed, CodePatternAttribute should be used instead.
  • ValueSet.Define has been removed, the property has been renamed in DSTU2 standard and moved out in DSTU3.
  • ValueSetExpander.Expand has been removed, the asynchronous version should be used instead.
  • ZipSource.Source has been removed, the ZipSource implements the IConformanceSource so should be used directly.

Behavioural changes

  • We have archived the Fhir.Metrics library. This means that Types/Quantity no longer supports UCUM unit conversion. Comparing units will now only succeed if the units are equivalent, at which point they are compared by value.
  • Narrative.div and Narrative.status are no longer in-summary (to reflect corrections made to this effect in the FHIR spec). This will result in a different rendering of resources when generating summaries.
  • Code<T> is now a subclass of Code, which means you only have to check against Code to know you're dealing with a code. The Value property returns a string when treating it as a Code, and will return an enum value otherwise. This change in hierarchy means that the order of type matches may be influenced, checking a Code<T> against Code was not a match before, but is now. Note that the single value in Code<T> actually has an implicit code and system that are defined for that code, so conceptually Code<T> is more like a Coding.
  • Accessing the Value property and most other methods on PrimitiveType will now throw a relevant CodedValidationException instead of generic InvalidCast/FormatException/InvalidOperation exceptions when its ObjectValue is incorrect.
  • ICoded.ToCodings() now returns an IReadOnlyCollection<Coding> instead of an IEnumerable<Codeing>. Since the implementations are all based on the standard list properties, it's more clear and efficient for us to actually indicate that the enumerables are in fact lists.

Minor/exotic breaking changes

  • Parameters.this[] has been removed in favor of a (future) use of this operator on the base, e.g. for dynamic access to elements. You can use the existing (and equivalent) GetSingle() instead.
  • ReflectionHelper.IsTypedCollection(System.Type) has been renamed to ReflectionHelper.IsTypedList(System.Type) since checking against ICollection<T> is not sufficient anymore to determine whether an element's type is a List, so we check against IList<T> instead.
  • The type of FhirEvaluationContext.TerminologyService has been changed from ITerminologyService to ICodeValidationTerminologyService. For most users, this should not be breaking.
  • The WithResourceOverrides static method introduced in 5.10 is now an extension method (and will need to be called on an instance). This allows subclasses to inherit it without implementing it themselves.
  • Moved ScopedNodeExtensions from the ElementModel namespace to the Model namespace. Import Hl7.Fhir.Model when using methods like Resolve()
  • The type of FhirEvaluationContext.ElementResolver has been changed from Func<string, ITypedElement> to Func<string, IScopedNode>. External resolvers should make sure to return a scoped node.
  • The DefaultModelFactory has been removed. It had not been in use for years, except by our own tests. With the cleanup of the PocoBuilder (ITypedElement->POCO) infrastructure, we decided to remove this class.
  • CitedArtifactContributorshipSummaryComponent has been renamed to the more correct ContributorshipSummaryComponent.
  • PropertyDeserializationContext and ObjectDeserializationContext have had their Path properties changed to be able to delay producing a path from a PathStack.
  • We had three classes dealing with Canonical across our software, and combined into one single class, the existing Canonical POCO. As part of this, CanonicalUriForFhirCoreType() has been renamed to ForCoreType().
Clone this wiki locally