Understanding Polymorphic Fields#
FHIR resources often include polymorphic fields - fields that can contain different types of data depending on the context. For example, a field might accept a date, a datetime, a period, or even a string in different scenarios. This flexibility is powerful but creates implementation challenges, especially in strongly-typed languages like Dart.
How FHIR-FLI Handles Polymorphism#
FHIR-FLI uses a consistent approach to handle polymorphic fields through abstract classes and clear naming conventions:
1. The "X" Naming Convention#
Polymorphic fields in FHIR-FLI are typically named with an "X" suffix (e.g., scheduledX,
productX, subjectX). This naming convention clearly indicates that the field can contain multiple possible types.
// Example from CarePlanDetail class
final ScheduledXCarePlanDetail? scheduledX;
final ProductXCarePlanDetail? productX;
2. Abstract Base Classes#
For each polymorphic field, FHIR-FLI defines an abstract class that all potential types for that field will implement:
// Example abstract class
abstract class ScheduledXCarePlanDetail extends DataType
implements PolymorphicType {}
// Classes that can be used in this field
class Timing extends BackboneType implements ScheduledXCarePlanDetail { ... }
class Period extends DataType implements ScheduledXCarePlanDetail { ... }
class FhirString extends PrimitiveType implements ScheduledXCarePlanDetail { ... }
This approach allows the compiler to verify type safety while maintaining flexibility.
3. Type-Specific Getters#
To make working with polymorphic fields more intuitive, FHIR-FLI includes type-specific getter methods that return the value only if it matches the requested type:
// Getters for the scheduledX field in CarePlanDetail
Timing? get scheduledTiming => scheduledX?.isAs<Timing>();
Period? get scheduledPeriod => scheduledX?.isAs<Period>();
FhirString? get scheduledString => scheduledX?.isAs<FhirString>();
These getters make it easy to access the field in its correct type without worrying about type casting or runtime errors.
Working with Polymorphic Fields#
Setting Values#
When creating or updating a FHIR resource, you can directly use any valid type for a polymorphic field:
// Setting a polymorphic field with a specific type
final carePlanDetail = CarePlanDetail(
status: CarePlanActivityStatus.inProgress,
scheduledX: Timing(
repeat: TimingRepeat(
frequency: FhirPositiveInt(1),
period: FhirDecimal(1),
periodUnit: UnitsOfTime.d,
),
),
// Other fields...
);
// Alternatively, create with a different type
final anotherCarePlanDetail = CarePlanDetail(
status: CarePlanActivityStatus.inProgress,
scheduledX: Period(
start: FhirDateTime.fromString('2023-01-01'),
end: FhirDateTime.fromString('2023-12-31'),
),
// Other fields...
);
The library handles the proper serialization and deserialization based on the type provided.
Checking and Accessing Types#
You can check the specific type of a polymorphic field and access it in several ways:
1. Using type-specific getters:
// Check if scheduledX is a Timing and access it
if (carePlanDetail.scheduledTiming != null) {
final timing = carePlanDetail.scheduledTiming;
// Work with timing...
}
2. Using the isAs<T>() method:
// Check if productX is a CodeableConcept
if (carePlanDetail.productX?.isAs<CodeableConcept>() != null) {
final concept = carePlanDetail.productX!.isAs<CodeableConcept>()!;
// Work with concept...
}
3. Using type checking:
if (carePlanDetail.productX is CodeableConcept) {
final concept = carePlanDetail.productX as CodeableConcept;
// Work with concept...
}
Important Considerations#
-
Single Type Limitation: A polymorphic field can only contain one type at a time. For example, if
scheduledXcontains aTiming, it cannot simultaneously contain aPeriodorFhirString. -
Type Safety: The abstract class implementation ensures that only valid types can be assigned to polymorphic fields, providing compile-time type safety.
-
Naming Patterns: When searching for fields by name, you can use:
- The base field name with an "X" suffix (e.g.,
scheduledX) - The specific type name (e.g.,
scheduledTiming,scheduledPeriod) - For
getChildrenByName(), using the specific type name will only return values if the field contains that specific type
- The base field name with an "X" suffix (e.g.,
-
Serialization: When serialized to JSON, the field will be appropriately labeled based on its type, following the FHIR specification.
Example with Polymorphic Types#
Here's a complete example showing how to work with the polymorphic scheduledX field in CarePlanDetail:
// Create a CarePlanDetail with a Timing scheduledX
final carePlanWithTiming = CarePlanDetail(
status: CarePlanActivityStatus.inProgress,
scheduledX: Timing(
repeat: TimingRepeat(
frequency: FhirPositiveInt(2),
periodUnit: UnitsOfTime.d,
),
),
);
// Create a CarePlanDetail with a Period scheduledX
final carePlanWithPeriod = CarePlanDetail(
status: CarePlanActivityStatus.inProgress,
scheduledX: Period(
start: FhirDateTime.fromString('2023-01-01'),
end: FhirDateTime.fromString('2023-12-31'),
),
);
// Accessing the values safely
void processSchedule(CarePlanDetail detail) {
// Approach 1: Using specific getters
if (detail.scheduledTiming != null) {
print('This is a timing-based schedule');
final frequency = detail.scheduledTiming?.repeat?.frequency?.valueInt;
print('Frequency: $frequency times');
} else if (detail.scheduledPeriod != null) {
print('This is a period-based schedule');
final start = detail.scheduledPeriod?.start?.valueDateTime;
final end = detail.scheduledPeriod?.end?.valueDateTime;
print('Period: $start to $end');
} else if (detail.scheduledString != null) {
print('This is a string-based schedule');
print('Description: ${detail.scheduledString?.valueString}');
}
}
This approach to polymorphic types allows FHIR-FLI to accurately represent the flexibility of the FHIR specification while maintaining the benefits of Dart's strong typing system.