This
page
is
part
of
the
FHIR
Specification
(v3.0.2:
(v4.0.1:
R4
-
Mixed
Normative
and
STU
3).
)
in
it's
permanent
home
(it
will
always
be
available
at
this
URL).
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
R4
R3
FHIR
Infrastructure
Work
Group
|
Maturity Level : N/A |
|
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"
"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" = tutorialmap "http://hl7.org/fhir/StructureMap/tutorial" = tutorialuses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as targetuses "http://hl7.org/fhir/StructureDefinition/tutorial-left" as source uses "http://hl7.org/fhir/StructureDefinition/tutorial-right" as targetgroup tutorial input "source" : TLeft as source input "target" : TRight as targetgroup tutorial(source src : TLeft, target tgt : TRight) { // rules go hereendgroup}
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 = asrc.a as a -> tgt.a = a "rule_a";
This simple statement says that:
"rule_a"
"rule_a"
is
a
purely
arbitrary
name
associated
with
the
rule
that
appears
in
logs,
error
messages,
trace
files,
etc.
It
has
no
other
meaning
in
the
mapping
statements.
Mostly,
in
fact,
it
is
simply
automatically
generated
by
the
engine.
It
will
not
be
specified
anymore
in
this
tutorial.
Note that there is no types explicitly in this mapping statement, but if the underlying system has types, then the types will have to be correct. If the underlying source and target trees are strongly typed, and the mapping groups have explicit types, then a short hand form is possible:
"rule_a" : for source.a make target.asrc.a -> tgt.a;
How
this
works
is
described
described
below
.
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 = bsrc.a1 as b -> tgt.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
|
|
There
are
3
different
ways
to
express
this
mapping,
depending
on
what
should
happen
when
the
length
of
source.a
src.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 a2.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 characterssrc.a2 as a -> tgt.a2 = truncate(a, 20); // just cut it off at 20 characters src.a2 as a where a2.length <= 20 -> tgt.a2 = a; // ignore it src.a2 as a check a2.length <= 20 -> tgt.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 does not automatically validate the output (e.g. it does not assume that it's the final step in the process).
Now for the case where there is a simple type conversion between the primitive types on the 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 a.isInteger make target.a21 = cast(a, "integer") // ignore it "rule_a21c" : for source.a21 as a where not at1.isInteger make target.a21 = 0 // just assign it 0src.a21 as a -> tgt.a21 = cast(a, "integer"); // error if it's not an integer src.a21 as a where a.isInteger -> tgt.a21 = cast(a, "integer"); // ignore it src.a21 as a where not at1.isInteger -> tgt.a21 = 0; // just assign it 0
More
than
one
of
these
mapping
rules
may
be
present
to
handle
all
possible
cases
-
.e.g
e.g.
rule_a21b
combined
with
rule_a21c.
Note that the mapping language does not itself define which primitive types exist. Typically, primitive types are defined by the underlying type system for the source and target trees, and the implementation layer makes these types available to the mapping language using the FHIRPath primitive types. The mapping language uses the FHIRPath syntax for primitive constants.
Back
to
the
simple
case
where
source.a22
src.a22
is
copied
to
target.a22,
tgt.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 = asrc.a22 as a -> tgt.a22 = a;
This will create one a22 in TRight for each a22 in TLeft.
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 // leave it to the transform engine rule_a23a : for source.a23 only_one as a make target.a23 = a // transform engine throws an error if there is more than one rule_a23b : for source.a23 first as a make target.a23 = a // Only use the first one rule_a23b : for source.a23 last as a make target.a23 = a // Only use the last onesrc.a23 as a -> tgt.a23 = a; // leave it to the transform engine src.a23 only_one as a -> tgt.a23 = a; // transform engine throws an error if there is more than one src.a23 first as a -> tgt.a23 = a; // Only use the first one src.a23 last as a -> tgt.a23 = a; // Only use the last one
Leaving
the
outcome
to
the
transform
engine
is
not
recommended;
it
may
might
not
always
know
whether
a
property
is
confined
to
a
single
value,
and
exactly
what
happens
is
unpredictable.
However
However,
there
are
some
circumstances
where
the
appropriate
action
is
to
defer
resolution,
so
this
is
allowed.
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
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 { // make aa exist rule_ab : for s_aa.ab as ab make t_aa.ab = ab // copy ab inside aa }src.aa as s_aa -> tgt.aa as t_aa then { // make aa exist s_aa.ab as ab -> 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
src.aa
and
target.aa,
tgt.aa,
and
assigns
2
variable
names
to
them.
Then,
the
rule
contains
an
additional
set
of
rules
(though
only
one
in
this
example)
to
map
with
the
context
of
s_aa
and
t_aa.
An alternate approach is to move the dependent rules to their own group:
rule_aa : for source.aa as s_aa make target.aa as t_aa then ab_content{s_aa, t_aa) // make aa exist group ab_content input src as source input tgt as targetsrc.aa as s_aa -> tgt.aa as t_aa then ab_content(s_aa, t_aa); // make aa existrule_ab : for src.ab as ab make tgt.ab = ab // copy ab inside aagroup ab_content(source src, target tgt) { src.ab as ab -> tgt.ab = ab; // copy ab inside aa }
Note that variables are divided into source and target; source variables are read-only, and cannot have their properties changed. Variable names may be reused in different contexts - they are only valid within the group or rule that defines them, and any dependent rules or groups.
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
|
|
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')src.d as d -> tgt.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
using
a
specified
concept
map,
and
then
to
put
the
code
value
of
the
return
translation
in
target.d.
tgt.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 FHIRPath 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 = isrc.i as i where m < 2 -> tgt.j = i; src.i as i where m >= 2 -> tgt.k = i;
Many/most
trees
are
fully
and
strongly
typed.
In
these
cases,
the
mapping
language
can
make
use
of
the
typing
system
to
simply
simplify
the
mapping
statements.
| Source Structure | Target Structure |
TLeft
aa : TLeftInner [0..*]
TLeftInner
ab : string [1..1]
|
TRight aa : : TRightInner [0..*] TRightInner ab : string [1..1] |
| The left instance is transformed to the right instance by copying aa to aa, and within aa, ab to ab | |
This is the same case as Step 7 above , but the mapping statements take advantage of the types:
rule_aa : for source.aa make target.aa group for types ab_content input src : TLeftInner as source input tgt : TRightInner as targetsrc.aa -> tgt.aa;rule_ab : for src.ab make tgt.abgroup ab_content(source src : TLeftInner, target tgt : TRightInner) <<types>> { src.ab -> tgt.ab; }
There are 2 different things happening in this short form:
group
for
types
-
the
In
the
case
of
the
first
rule
(rule_aa),
the
engine
finds
a
need
to
map
aa
to
aa
and
determines
that
it
must
map
from
TLeftInner
to
a
TRightInner.
Since
a
group
is
defined
for
this
purpose,
it
creates
a
TRightInner
in
,
target.aa,
tgt.aa,
and
then
applies
the
discovered
rule
as
a
dependency
rule.
Inside
that
rule
that
instructs
the
mapping
engine
to
make
tgt.ab
from
src.ab.
It
knows
that
both
are
primitive
types,
and
compatible,
and
can
apply
this
correctly.
This
short
form
is
only
applicable
when
there
is
only
one
source
and
target,
when
the
types
of
both
are
known,
and
when
no
other
dependency
rules
are
nominated.
If the target element is polymorphic (can have more than one type), then the correct type of the target can only be inferred from the source type:
group for type+types ab_content input src : TLeftInner as source input tgt : TRightInner as target rule_ab : for src.ab make tgt.abgroup ab_content(source src : TLeftInner, target tgt : TRightInner) <<type+>> { src.ab -> tgt.ab; }
Not only is this group the default for (TLeftInner:TRightInner), if the engine has a TLeftInner with an unknown target type, it should create a TRightInner, and proceed as above.
It is an error if the engine locates more than one group of rules claiming to be the correct group for a type pair of a single source type.
It's
now
time
to
start
maving
moving
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
|
|
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" }src.e as s_e -> tgt.e as t_e then { for s_e -> 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" }src.f as s_f -> tgt.e as t_e first then { s_f -> t_e.f = s_f, t_e.g = 'g2'; };
The second example for reworking structure moves cardinality around the hierarchy. 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
|
|
The
key
to
setting
this
mapping
up
is
to
create
a
variable
context
for
source.az1,
src.az1,
and
then
carry
it
down,
performing
the
actual
mappings
at
the
next
level
down:
// setting up a variable for the parentaza : for src.az1 as s_az1 then {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 {// one tgt.az1 for each az3 s_az1.az3 as s_az3 -> tgt.az1 as t_az1 then { // value for az2. Note that this refers to a previous context in the sourceaz2 : for s_az1.az2 as az2 make t_az1.az2 = az2s_az1.az2 as az2 -> t_az1.az2 = az2; // value for az3az3 : for s_az3 make tgt_az1.az3 = src_az3 } }s_az3 -> 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
|
|
The
key
to
setting
this
mapping
up
is
to
create
a
variable
context
for
source.az1,
src.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 }src.f1 as s_f1 -> create("TRight2") as rr, tgt.ptr = reference(rr) then { s_f1 -> rr.f2 = srcff; };
This
mapping
statement
makes
use
a
special
known
value
"null"
"null"
for
the
target
context
to
indicate
that
the
created
element/object
of
type
"TRight2"
"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 usually 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
|
|
The
first
task
of
the
map
is
to
ask
the
application
host
to
find
the
structure
identified
by
source.ptr,
src.ptr,
and
create
a
variable
for
it
f2: [todo]
f2: [todo]