What is FHIRPath?
FHIRPath is a path-based language, similar to XPath, that allows you to navigate a FHIR resource. It powers evaluation and data extraction from its fields to add business validations to profiles on a FHIR server.
In this article, we’ll take a look at the different types of functions you can perform to validate resources with FHIRPath. To bring you more value, we’ve decided to examine the functions not extensively covered by the official FHIR specification. We’ve also added real-life FHIRPath examples we’ve used in the development of distinct national medical systems. You’ll be able to learn about different FHIRPath use cases and examine various FHIRPath expression examples.
Importance of FHIRPath in Profile Validation
FHIRPath plays a critical role in validating FHIR profiles by providing a standardized way to enforce constraints and business logic across FHIR resources. Unlike other validation methods, FHIRPath enables developers to express complex validation rules that handle relationships between resource elements, conditional constraints, and collections.
FHIRPath is highly expressive, allowing for the creation of more sophisticated and accurate validation rules compared to traditional XML or JSON schema validations. Moreover, FHIRPath offers flexibility by supporting all FHIR resource types and is designed to work across platforms, making it adaptable for different implementations. It is efficient at querying and navigating complex, nested structures within FHIR resources, making validation both effective and scalable.
How to add FHIRPath validations
You can create resource validations through different functions, which are added to constraints. This makes writing validations significantly easier for business analysts, as they don’t have to be hard-coded.
There are certain cases when it’s better to use FHIRPath than other types of validations, and vice versa. (Read more about different types of FHIR validations.)
For example, when there are a lot of different validations needed on different fields, it’s better to use slicing. However, when you need to perform only one single validation on different fields, it’s better to use FHIRPath.
To create a validation with FHIRPath, a constraint could be added either on the resource level or on the field level. Depending on the level, the validation will check and work with more or fewer fields in the resource.
Below is an example of an added constraint:
"resourceType": "StructureDefinition",
…
"constraint": [
{
"key": "123",
"severity": "error",
"human": "Only encounter which exists in the bundle should be referenced in Composition",
"expression": "entry.resource.where(is(Encounter)).id = entry.resource.where(is(Composition)).encounter.reference.replace('Encounter/','')",
"source": "http://hl7.org/fhir/StructureDefinition/Bundle"
}
]
FHIRPath functions for resource validation
1. Function iif: add if-then-else validation to a profile
Use case:
We use this function when we want to add if-then-else validation to a profile.
Validation rule:
Human: “Only a medication request with MY_profile is accepted.”
FHIRPath Expression:
"iif(entry.resource.where(is(MedicationRequest)).exists(), entry.resource.where(is(MedicationRequest)).meta.select(profile)='http://example.com/fhir/StructureDefinition/My_profile', entry.resource.where(is(MedicationRequest)).exists().not())"
Current validation is done for the MedicationRequest resource type.
Validation: The iif operation has three parts, divided by a comma (,). If the first part is true, then we check that the second part is also true. If the second part is false, then we check that the third part is true.
Example: Below, we validate the resource MedicationRequest against the StructureDefinition that contains the iif expression in the constraint. The example is valid because the profile for resource MedicationRequest is http://example.com/fhir/StructureDefinition/my_bundle. The example would also be valid if there were zero MedicationRequest resources in the bundle.
{
"resourceType": "Bundle",
"id": "bundle_id",
"type": "transaction",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_bundle"
]
},
"timestamp": "2022-08-02T06:00:00+00:00",
"entry": [
{
"resource": {
"resourceType": "MedicationRequest",
"id": "example_medreq",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/MY_profile"
]
},
"status": "active",
"intent": "plan",
"encounter": {
"reference": "Encounter/encounter_id"
},
"medicationReference": {
"reference": "Medication/medication_id"
},
"subject": {
"reference": "Patient/patient_id"
}
},
"request": {
"method": "POST",
"url": "MedicationRequest"
}
}
]
}
Another use of the iif function is to set a validation for a period of time.
Validation rule:
Human: “If onset[x] period.end is provided, it should be less than or equal to the current date.”
FHIRPath Expression:
"iif(onset is Period and onset.end.exists(), onset.end.toDate() <= today(), onset is Period.not() or onset.end.exists().not())"
Current validation is done for the AllergyIntolerance resource type.
Validation: The below examples are for the AllergyIntolerance resource. Here, if onset is present and is period, then we check that period.end is in the past. If it is in the past, then the function returns true. If onset does not exist or period.end does not exist, then the validation will also return true.
Both examples below are valid, because period.end is either later than today or does not exist.
Example 1: The example is true because onsetPeriod.end is less than today.
{
"resourceType": "AllergyIntolerance",
"id": "nkla",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_profile"
]
},
"clinicalStatus": {
"coding": [
{
"display": "Active"
}
]
},
"onsetPeriod": {
"start": "2022-08-02",
"end": "2022-08-02"
},
"encounter": {
"reference": "http://example.com/Encounter/example"
},
"code": {
"coding": [
{
"display": "No Known Latex Allergy (situation)"
}
]
},
"patient": {
"reference": "http://example.com/Patient/example"
},
"recordedDate": "2022-08-02T15:37:31-06:00",
"recorder": {
"reference": "http://example.com/Practitioner/example"
}
}
Example 2:The second example is also valid because we do not have onsetPeriod at all.
{
"resourceType": "AllergyIntolerance",
"id": "nkla",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_profile"
]
},
"clinicalStatus": {
"coding": [
{
"display": "Active"
}
]
},
"encounter": {
"reference": "http://example.com/Encounter/example"
},
"code": {
"coding": [
{
"display": "No Known Latex Allergy (situation)"
}
]
},
"patient": {
"reference": "http://example.com/Patient/example"
},
"recordedDate": "2022-08-02T15:37:31-06:00",
"recorder": {
"reference": "http://example.com/Practitioner/example"
}
}
2. Function select(), where(): return a concrete field in the resource and validate it according to something
Use case:
We use this validation when we need to return a concrete field in the resource and validate it according to something.
Validation rule:
Human: “Meta profile Medication is not consistent with http://example.com/fhir/StructureDefinition/my_profile.“
Current validation is done for the Bundle resource type.
Expression:
"entry.resource.where(is(Medication)).meta.select(profile)='http://example.com/fhir/StructureDefinition/my_profile'"
Validation: With this function we validate that there should be resource Medication with profile http://example.com/fhir/StructureDefinition/my_profile in the Bundle. (This validation could also be done with slicing-type profiles. Read more about slicing and how it works.)
As you can see, this validation does the same as the validation above (iif), so it validates if a profile is “my profile.” So, we can see that the FHIRPath language is very flexible. The difference between these validations is that here the field with the profile is mandatory; with iif the field is optional.
Example:
{
"resourceType": "Bundle",
"id": "bundle_id",
"type": "transaction",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_bundle"
]
},
"timestamp": "2022-08-02T06:00:00+00:00",
"entry": [
{
"resource": {
"resourceType": "Medication",
"id": "example_med",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_profile"
]
},
"status": "active",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/ndc",
"code": "0409-6531-02",
"display": "Vancomycin Hydrochloride (VANCOMYCIN HYDROCHLORIDE)"
}
]
},
"ingredient": [
{
"itemCodeableConcept": {
"coding": [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "66955",
"display": "Vancomycin Hydrochloride"
}
]
},
"isActive": true,
"strength": {
"numerator": {
"value": 500,
"system": "http://unitsofmeasure.org",
"code": "mg"
},
"denominator": {
"value": 10,
"system": "http://unitsofmeasure.org",
"code": "mL"
}
}
}
]
},
"request": {
"method": "POST",
"url": "Medication"
}
}
]
}
3. Function replace(): change any field in the resource or replace anything with an empty string
Use Case:
We use this validation when we need to change any field in the resource. We could also replace anything with an empty string.
Current validation is done for the Medication resource type.
Validation rule:
Human: “The only encounter that exists in the bundle should be referenced in Composition.”
FHIRPath Expression:
"entry.resource.where(is(Medication)).id = entry.resource.where(is(MedicationRequest)).medicationReference.reference.replace('Medication/','')"
Validation: Here we say that the id of the resource should be the same as the string after Medication/ in Bundle.entry.resource.medicationReference.reference. Literally, we remove Medication/ and check that id in the resource Medication is the same as id in Bundle.entry.resource.medicationReference.reference without Medication/.
Example:
{
"resourceType": "Bundle",
"id": "bundle_id",
"type": "transaction",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_bundle"
]
},
"timestamp": "2022-08-02T06:00:00+00:00",
"entry": [
{
"resource": {
"resourceType": "MedicationRequest",
"id": "example_medreq",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/MY_profile"
]
},
"status": "active",
"intent": "plan",
"encounter": {
"reference": "Encounter/encounter_id"
},
"medicationReference": {
"reference": "Medication/medication_id"
},
"subject": {
"reference": "Patient/patient_id"
}
},
"request": {
"method": "POST",
"url": "MedicationRequest"
}
},
{
"resource": {
"resourceType": "Medication",
"id": "medication_id",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_profile"
]
},
"status": "active",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/ndc",
"code": "0409-6531-02",
"display": "Vancomycin Hydrochloride (VANCOMYCIN HYDROCHLORIDE)"
}
]
}
},
"request": {
"method": "POST",
"url": "Medication"
}
}
]
}
4. Function: Regex in FHIRPath: validate the data a user could send to us
Use case:
We use this FHIRPath validation when we want to validate the data a user could send to us.
Validation rule:
Human: “Email must be valid.”
FHIRPath Expression:
"system != 'email' or value.matches('^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$')"
If we want to validate a phone number instead:
Validation rule:
Human: “Phone must be valid.”
One more expression with regex for the same example:
"system != 'phone' or value.matches('^\\+\\d{7,15}$')"
Current validation is done for the Patient resource type.
In this example, we validate that the email is a real address that could exist. The same example would apply if we wanted to validate a phone number or another type of data a user sends to the server.
{
"resourceType": "Patient",
"meta": {
"profile": [
"http://example.com/fhir/StructureDefinition/my_patient_prifile"
]
},
"active": true,
"telecom": [
{
"system": "phone",
"value": "+1234567890",
"use": "mobile"
},
{
"system": "email",
"value": "[email protected]"
}
],
"gender": "male",
"birthDate": "1993-10-02"
}
Conclusion
We hope our practical FHIRPath validation examples will help you gain a better understanding of how to use FHIRPath to add validations. This guide is part of a series of articles aimed at making FHIR implementation more straightforward.
Our team of business analysts and FHIR engineers is always open for consultations. Contact us about your project, and we’ll ensure that your FHIR adoption goes smoothly.