This page is part of the FHIR Specification (v1.8.0: STU 3 Draft). The current version which supercedes this version is 5.0.0. For a full list of available versions, see the Directory of published versions . Page versions: R5 R4B R4 R3
FHIR Infrastructure Work Group | Maturity Level: 0 | Ballot Status: n/a yet |
This tutorial introduces the FHIR mapping language.
To start with, we're going to consider a very simple case: mapping between two structures that have the same definition, a single element with the same name and the same primitive type:
Source Structure | Target Structure |
TLeft a : string [0..1] | TRight a : string [0..1] |
The left instance is transformed to the right instance by copying a to a |
Note that for clarity in this tutorial, all the types are prefixed with T.
The first task to do is to set up the mapping context on a default group. All mappings are divided up into a set of groups. For now, we just set up a group named "tutorial" - the same as the name of the mapping. For this tutorial, we also declaring the source and target models, and specify that an application invokes this with a copy of the left (source) instance, and also an empty copy of the right (target) instance:
map "http://hl7.org/fhir/StructureMap/tutorial" = tutorial uses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as target group tutorial input "source" : TLeft as source input "target" : TRight as target // rules go here endgroup
Note that the way the input variables are set up is a choice: we choose to provide the underlying type definitions on which both source and target models are based, and we choose to specify that the invoking application most provide both the source and the target instance trees. Other options are possible; these are discussed further below. The rest of the tutorial examples use the same setup for the group.
Having set up the context, we now need to define the relationships between the source and target structures:
"rule_a" : for source.a as a make target.a = a
This simple statement says that:
Note that the types don't feature explicitly in the mapping statements.
Now consider the case where the elements have different names:
Source Structure | Target Structure |
TLeft a1 : string [0..1] | TRight a2 : string [0..1] |
The left instance is transformed to the right instance by copying a1 to a2 |
This relationship is a simple variation of the last:
"rule_a1" : for source.a1 as b make target.a2 = b
Note that the choice of variable name is purely arbitrary. It does not need to be the same as the element name.
Still sticking with very simple mappings, let's consider the case where there is a length restriction on the target model that is shorter than the one on the source model - in this case, 20 characters.
Source Structure | Target Structure |
TLeft a2 : string [0..1] | TRight a2 : string [0..1] {maxlength = 20} |
The left instance is transformed to the right instance by copying a2 to a2, but target.a2 can only be 20 characters long |
There are 3 different ways to express this mapping, depending on what should happen when the length of source.a is > 20 characters:
"rule_a20a" : for source.a2 as a make target.a2 = truncate(a, 20) // just cut it off at 20 characters "rule_a20b" : for source.a2 as a where a1.length <= 20 make target.a2 = a // ignore it "rule_a20c" : for source.a2 as a check a2.length <= 20 make target.a2 = a // error if it's longer than 20 characters
Note that it is implicit here that the transformation engine is not required to expected to validate the output against that underlying structure definitions that may apply to it. An application may - and usually should - validate the outputs after the transforms, but the transform engine itself should not assume that it's the final step in the process and automatically validate the output.
Now for the case where there is a simple type conversion between left and right, in this case from a string to an integer.
Source Structure | Target Structure |
TLeft a21 : string [0..1] | TRight a21 : integer [0..1] |
The left instance is transformed to the right instance by copying a21 to a21, but a21 is converted to an integer |
There are 3 different ways to express this mapping, depending on what should happen when a is not an integer:
"rule_a21a" : for source.a21 as a make target.a21 = cast(a, "integer") // error if it's not an integer "rule_a21b" : for source.a21 as a where a1.isInteger make target.a2 = cast(a, "integer") // ignore it "rule_a21c" : for source.a21 as a where not at1.isInteger make target.a21 = 0 // just assign it 0
Back to the simple case where source.a22 is copied to target.a22, but in this case, a22 can repeat (in both source and target):
Source Structure | Target Structure |
TLeft a22 : string [0..*] | TRight a22 : string [0..*] |
The left instance is transformed to the right instance by copying a22 to a22, once for each copy of a22 |
The transform rule simply asserts that a22 maps to a22. The engine will apply the rule once for each instance of a22:
"rule_a22" : for source.a22 as a make target.a22 = a
A more difficult case is where the source allows multiple repeats, but the target doesn't:
Source Structure | Target Structure |
TLeft a23 : string [0..*] | TRight a23 : integer [0..1] |
The left instance is transformed to the right instance by copying a23 to a23, but there can only be one copy of a23 |
Again, there are multiple different ways to write this, depending on out desired outcome if there is more than one copy of a23:
rule_a23a : for source.a23 as a make target.a23 = a { only_one } // transform engine creates an error rule_a23b : for source.a23 as a make target.a23 = a // leave error to lower layer; less informative rule_a23c_1 : for source.a23 { first } as a make target.a23 = a // only use the first item in the list
The last rule is the first example of stringing rules together. In this case, rule_a23c_1 exists to define a variable for the list so that the where condition can compare the items in the list against a criteria expressed against another variable. The ability to compare conditions amongst variables like this in the where clause is a powerful technique that will explored further below.
Most transformations involve nested content. Let's start with a simple case, where element aa contains ab:
Source Structure | Target Structure |
TLeft aa : [0..*] ab : string [1..1] | TRight aa : [0..*] ab : string [1..1] |
The left instance is transformed to the right instance by copying aa to aa, and within aa, ab to ab |
Note that there is no specified type for the element aa. Some structure definitions (FHIR resources) do leave these elements as anonymously typed, while others explicitly type them. However since the mapping does not refer to the type, it's literal type is not important.
rule_aa : for source.aa as s_aa make target.aa as t_aa then rule_ab(s_aa, t_aa) // make aa exist rule_ab : for s_aa.ab as ab make t_aa.ab = ab // copy ab inside aa
This situation is handled by a pair of rules: the first rule establishes that relationship between source.aa and target.aa, and assigns 2 variable names to them. Then, the rule says to check other rules for mappings involving s_aa and t_aa, and apply any other rules that match. In this case, rule_ab matches, and so for every source.aa, source.ab will be copied to target.aa.
Note that the source and target variables never overlap, so both of the variables could have been named 'aa' with no ambiguity, but overloading the names like this is avoided in this tutorial due to the potential for reader confusion.
In this case, the first rule specified for the any additional rules involving the variables defined by applying the rule. This is a very flexible way to set up the mapping arrangements, but requires discipline in the way that variables are named so as not to get the rules crossed.
A common translation pattern is to perform a translation e.g. from one set of codes to another
Source Structure | Target Structure |
TLeft d : code [0..1] | TRight d : code [0..1] |
The left instance is transformed to the right instance by translating source.d from one set of codes to another |
The key to this transformation is the ConceptMap resource, which actually specifies the mapping from one set of codes to the other:
rule_d : for source.d as d make target.d = translate(d, 'uri-of-concept-map', 'code')
This asks the mapping engine to use the $translate operation on the terminology server to translate the code, and then to put the code value of the return translation in target.d.
Another common translation is where the target mapping for one element depends on the value of another element.
Source Structure | Target Structure |
TLeft i : string [0..1] m : integer [1..1] | TRight j : [0..1] k : [0..1] |
How the left instance is transformed to the right instance depends on the value of m: if m < 2, then i maps to j, else it maps to k |
This is managed using conditions on the mapping statements:
rule_i1 : for source.i as i where m < 2 make target.j = i rule_i2 : for source.i as i where m >= 2 make target.k = i
It's now time to start maving away from relatively simple cases to some of the harder ones to manage mappings for. The first mixes list management, and converting from a specific structure to a general structure:
Source Structure | Target Structure |
TLeft e : string [0..*] f : string [1..1] | TRight e : [0..*] f : string [1..1] g : code [1..1] |
The left instance is transformed to the right instance by adding one instance of target.e for each source.e, where the value goes into target.e.f, and the value of target.e.g is 'g1'. source.f is also transformed into the same structure, but the value of target.e.g is 'g2'. As an added complication, the value for source.f must come first |
This leads to some more complex mapping statements:
ef_a1: for source.e as s_e make target.e as t_e then { ef_a2: for s_e make t_e.f = s_e, t_e.g = "g1" } ef_b1: for source.f as s_f make target.e as t_e { first } then { ef_b2: for s_f make t_e.f = s_f, t_e.g = "g2" }
The second example for reworking structure moves cardinality around the heirarchy. in this case, the source has an optional structure that contains a repeating structure, while the target puts the cardinality at the next level up:
Source Structure | Target Structure |
TLeft az1 :[0..1] az2 : string [1..1] az3 : string [0..*] | TRight az1 :[0..*] az2 : string [1..1] az3 : string [0..1] |
The left instance is transformed to the right instance creating on target.az1 for every source.az1.az3, and then populating each az1 with the matching value of az3, and copying the value of az2 to each instance |
The key to setting this mapping up is to create a variable context for source.az1, and then carry it down, performing the actual mappings at the next level down:
// setting up a variable for the parent aza : for src.az1 as s_az1 then { // one target.az1 for each az3 azb : for s_az1.az3 as s_az3 make target.az1 as t_az1 then { // value for az2. Note that this refers to a previous context in the source az2 : for s_az1.az2 as az2 make t_az1.az2 = az2 // value for az3 az3 : for s_az3 make tgt_az1.az3 = src_az3 } }
Simple mappings, such as we've dealt with so far, where the source and target structure both have the same scope, and there is only one of each, are all well and good, but there are many mappings where this is not the case. There is a set of complications when dealing with multiple instances:
For our first example, we're going to look at creating multiple output structures from a single input structure.
Source Structure | Target Structure |
TLeft f1 : String [0..*]; | TRight ptr : Resource(TRight2) [0..*] TRight2 f1 : String [1..1]; |
The left instance is transformed to the right instance creating a copy of TRight2 for each f1 in the source, and then putting the value of source.f1 in TRight2.f1 |
The key to setting this mapping up is to create a variable context for source.az1, and then carry it down, performing the actual mappings at the next level down:
f1 : for source.f1 as s_f1 make create("TRight2") as rr, target.ptr = reference(rr) then { f1a: for s_f1 make rr.f2 = srcff }
This mapping statement makes use a special known value "null" for the target context to indicate that the created element/object of type "TRight2" doesn't get added to any existing target context. Instead, it will only be available as a context in which to perform further mappings - as rule f1a does.
The mapping engine passes the create request through to the host application, which is using the mapping. It must create a valid instance of TRight, and identify it as appropriate for the technical context in which the mapping is being used. The reference transform is also passed back to the host application for it to determine how to represent the reference - but this is usally some kind of URL.
For our second example, we're going to look at the reverse: where multiple input structures create a single input structure.
Source Structure | Target Structure |
TLeft ptr : Resource(TLeft2) [0..*] TLeft2 f2 : String [0..*]; | TRight f2 : String [1..*]; |
The left instance is transformed to the right instance finding each ptr reference, getting it's value foe f1, and adding this in target.f2 |
The first task of the map is to ask the application host to find the structure identified by source.ptr, and create a variable for it
f2: [todo]