FHIRPath Engine
The fhir_r4_path library provides a Dart implementation of the FHIRPath specification, allowing you to query and manipulate FHIR resources using standardized expressions. This library follows the official HL7 FHIRPath specification and integrates seamlessly with the fhir_r4 package.
Installation
dependencies:
fhir_r4_path: ^0.4.3
fhir_r4: ^0.4.2
Basic Usage
There are two main approaches to using the FHIRPath engine:
1. Simple Approach: walkFhirPath (async)
The walkFhirPath function provides a straightforward way to evaluate a FHIRPath expression against a FHIR resource without having to manage an engine instance yourself:
import 'package:fhir_r4/fhir_r4.dart';
import 'package:fhir_r4_path/fhir_r4_path.dart';
void main() async {
// Create a patient resource
final patient = Patient(
name: [
HumanName(
family: 'Doe'.toFhirString,
given: ['John'.toFhirString],
use: HumanNameUse.official,
),
],
);
// Evaluate a FHIRPath expression
final result = await walkFhirPath(
context: patient,
pathExpression: "Patient.name.where(use = 'official').family",
);
// The result will be a List<FhirBase>
print(result.map((e) => e.toString()).join(', ')); // Outputs: Doe
}
Parameters for walkFhirPath:
context: The FHIR resource to query (required)pathExpression: The FHIRPath expression to evaluate (required)resource: The resource that contains the original node (defaults to context if null)rootResource: The container resource (if applicable)environment: A map of environment variables (keys must start with %)
2. Engine Approach: FHIRPathEngine (preferred for performance)
When you have multiple FHIRPath expressions or you need to evaluate many resources in a loop, the recommended approach is to create a reusable FHIRPathEngine instance. This is more efficient because you only parse expressions once and can share internal structures.
Creating the Engine
Because the engine initialization is asynchronous, you'll use the static create(...) factory method:
import 'package:fhir_r4/fhir_r4.dart';
import 'package:fhir_r4_path/fhir_r4_path.dart';
void main() async {
// Create the FHIRPath engine (async)
final fhirPathEngine = await FHIRPathEngine.create(WorkerContext());
// Parse the expression once (can be reused)
final node = fhirPathEngine.parse("Patient.name.where(use = 'official').family");
// Create some resources to test against
final patient1 = Patient(
name: [
HumanName(
family: 'Smith'.toFhirString,
given: ['Jane'.toFhirString],
use: HumanNameUse.official,
),
],
);
final patient2 = Patient(
name: [
HumanName(
family: 'Brown'.toFhirString,
given: ['Charlie'.toFhirString],
use: HumanNameUse.official,
),
],
);
// Evaluate against different patients
final result1 = await fhirPathEngine.evaluate(patient1, node);
final result2 = await fhirPathEngine.evaluate(patient2, node);
print(result1); // [Smith]
print(result2); // [Brown]
}
Evaluating Expressions
Once you have the FHIRPathEngine instance:
parse(String expression): Parses and returns anExpressionNode.evaluate(FhirBase? base, ExpressionNode node): Evaluates the parsed expression against the base resource.evaluateWithContext(...): Allows you to provide additional context/resources or environment variables.
final result3 = await fhirPathEngine.evaluateWithContext(
null, // appContext (any object, e.g., a custom context or null)
patient1, // focusResource
null, // rootResource
patient1, // base (the immediate context for evaluation)
node,
environment: {
'%var1': [42.toFhirInteger],
'%var2': ['test'.toFhirString],
},
);
Common FHIRPath Expressions
Here are some examples of common FHIRPath expressions:
Basic Navigation
// Access a field
Patient.active
// Access an array element
Patient.name[0]
// Access a field in an array
Patient.name.family
// Access a field with a specific value
Patient.name.where(use = 'official')
Functions
// Count elements
Patient.name.count()
// Check if telecom exists
Patient.telecom.exists()
// Filter with conditions
Patient.telecom.where(system = 'phone')
// First/last elements
Patient.name.first()
Patient.name.last()
Operators
// Equality
Patient.gender = 'male'
// Comparison
Patient.name.count() > 1
// Logical operators
Patient.active = true and Patient.deceased = false
Environment Variables
FHIRPath supports environment variables, which must be prefixed with %:
final environment = {
'%pi': [3.14159.toFhirDecimal],
'%today': [FhirDateTime('2023-01-01')],
};
final result = await walkFhirPath(
context: patient,
pathExpression: "Patient.birthDate < %today",
environment: environment,
);
Advanced Features
Resource Cache
For advanced scenarios, the library provides a ResourceCache abstract class to cache canonical resources (like CodeSystem, ValueSet, StructureDefinition), potentially saving time on repeated lookups:
abstract class ResourceCache {
Future<T?> getCanonicalResource<T extends CanonicalResource>(
String url, [String? version]
);
Future<void> saveCanonicalResource(CanonicalResource resource);
// ...
}
Canonical Resource Manager
The CanonicalResourceCache extends ResourceCache to provide version-aware storage and retrieval of resources:
import 'package:fhir_r4/fhir_r4.dart';
import 'package:fhir_r4_path/fhir_r4_path.dart';
// Create a manager
final manager = CanonicalResourceCache();
// Store a canonical resource
final valueSet = ValueSet(
url: 'http://example.org/fhir/ValueSet/my-codes'.toFhirUri,
version: '1.0.0'.toFhirString,
name: 'MyCodes'.toFhirString,
);
manager.see(valueSet);
// Retrieve by URL
final retrieved = await manager.getCanonicalResource<ValueSet>(
'http://example.org/fhir/ValueSet/my-codes'
);
// Retrieve by URL and version
final specificVersion = await manager.getCanonicalResource<ValueSet>(
'http://example.org/fhir/ValueSet/my-codes',
'1.0.0'
);
Key features of CanonicalResourceCache:
- Version-aware storage and retrieval
- Compatibility with semantic versioning
- Optional lazy loading through proxies
- Integration with HTTP clients for remote resource fetching