Skip to content

Instantly share code, notes, and snippets.

Last active November 19, 2021 12:22
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?

Proposed Solution to Mappings

For the sake of making the argument easy to understand, I decided to represent the data as simple maps and assert facts about them based on what we know. If I'm assuming something that I'm not sure about then I marked them, so we can verify or investigate them separately.

For this to work let's forget about MedicationRequest, R3, R4, etc. All we care about is the raw data (like json) and the type after decoding it. For example, a MedicationRequest of STU3 version can be represented as type A. Mixing the actual resource name with its version only makes understanding the problem harder without any contribution to the solution.

Raw data and Profile

Here is an example of an input data:

{"a": "x",
 "b": "y"}

We know that a profile p1 can change the semantic of this data but not it's type. i.e. profile p1 cannot produce:

{"a": "x",
 "b": "y",
 "c": "z"}

Profile p1 can add extensions ex to any element[1]. Both of these are valid objects:

{"a": "x",
 "b": "y",
 "ex": ["z"]}
{"a": {"ex":  ["x"]},
 "b": "y"}

It's important to note that profile p1 is not an operation. It just represents the meaning of a value and how message processor system can interpret it. If our system is receiving:

{"a": "x",
 "b": "y",
 "ex": ["z"]}

I can still decode the object and assign a type to it. In another word, the type of data is preserved despite its profile(s). This implies two things:

  • "p": "p1" is optional because it's just a hint and does not translate to an operation.
  • The only way of adding more information is done via extensions.
  • Profile may change the meaning of something, like "x" means something else, but we don't care about it because, we are only interested in the shape (i.e. type) of an object not it's meaning[2].



  1. We know the type of raw data before processing it[3].

In our system we will deal with raw data in form of json mostly. That means we need to decode the data to some type. I'll use lowercase letters to represent the object value (an instance) and uppercase letter to represent its type. Also, data is represented with the letter d. This can be json or xml.

d₁ = {"a": "x", "b": "y"}
a:A = {a: "x", b: "y"}

d₂ = {"a": "x", "b": "y", "c": "z"}
b:B = {a: "x", b: "y", c: "z"}

We have a decoder function f that receives data d₁ and returns a:A. It's possible to do f(d2) but we lost data that is in A but not in B[4].

Converter vs Transformer

Our goal is to create a converter function where c(a:A) = b:B and c(b:B) = a:A. For simplicity's sake, from now on we only consider converting from A to B. All the assumptions and methods discussed, applies to converting other way around.

Another function of interest is the transformer. The distinction between a converter and a transformer is subtle but making this distinction is utmost important.

A transformer only changes one attribute, so that the result is the same basic type, but of a different capacity or intensity (voltage for example). A converter, on the other hand makes the resulting object different in type from then initial condition, for example changing alternating current to direct current.

This is not a just a terminology definition for the argument sake. Instead, it's an exact mirror of the actual implementation. What we call "base converter" is implemented as a method convertResource: A->B and the method to use StructureMaps is called transform: A->A[5]. This implies that we can use a converter when we want to change the type whereas transformer is used to change the attributes of data i.e. profiles.

The problem

We've defined some terms related to the system and observed their properties. Now we can use it to define (or at least revisit) the problem.

We want to create a converter c that converts resource of type A to B. We know that the attributes of A has changed due to existence of profile(s). In order to state the problem and also discuss possible solution(s) first we need to define some more terms. As before, we use a:A to describe object of type A however, now we also make a distinction when the object a has extended/changed with a profile. aₚ₁:A has the same type A but, its attributes has extended/changed by the profile p₁.

So we need to implement: c(aₚ₁:A) = bₚ₁:B


We can now discuss different solutions to the above problem.

Using both converter and transformer


  1. converter is lossless over input type A[6]
  2. convert is lossless over p₁ attributes. That's it: c(aₚ₁:A) = sₚ₁:B ;notice sₚ₁ instead of bₚ₁[7]

For this solution to work we need a transformer such that: t(sₚ₁:B) = bₚ₁:B. This can be done because the profile information is not lost during convert operation (second assumption). The FML for this solution only concerns with transforming the attributes that p₁ is describing.

We already know that the second assumption is not true[8]. But we know we can take advantage of advisor to make it possible. Advisors are not just for converting extensions. We can use the ignoreExtension method to make converter lossless around attributes defined via p₁.

Using just advisor


  1. Attributes of p₁ is carried over from type A to B without any change.
  2. Above should be true for all profiles.

This solution only works if the logic for conversion, requires only preserving p₁ and not changing the attributes. One can observe the solution can be simplified to c(aₚ₁:A) = sₚ₁:B. That means we only need to make converter lossless over p₁. In another word, we only need an advisor to signal converter not to drop/ignore extensions.


  1. Extensions are not the only thing that a profile can add. The FHIR profiling describes what is possible to do with profiles. The main takeaway is a profile (or a set of them), no matter how complicated, only changes the attributes of data and not it's type. There is only one exception to this and that's when a profile introduces a new resource.
  2. FHIR documentation makes a difference between a message processor and a system that just moves messages around. A message processor must care about the semantics of a message. An example is when a message is marked as modifier. See docs
  3. This assumption holds true because, in our system, the type of message is given to us via Content-Type header.
  4. We can't prove this behaviour for all types (we've only confirmed it for MedicationRequest) but whether this holds or not, is not relevant. If there is an encoder that can receive type A and produces type B then, what's the purpose of a converter?
  5. One can say, this naming is just a coincidence and, it's very likely that it is so. However, it doesn't change the fact that the method signature matches exactly with the definition. Transform is A->A and convert is A->B.
  6. This assumption holds true because, as we've seen with MedicationRequest, the onBehalfOf field is preserved during conversion. Is this true for all other resources/types?
  7. This assumption is not true but can be made so. Keep reading.
  8. According to documentation, modified: true is an exception to this. (todo: This needs reference) and implementation to prove.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment