Using IReadOnlyDictionary
with POCOs
All POCOs in the SDK implement the IReadOnlyDictionary
interface, through which the POCO’s data can be accessed dynamically.
This interface is implemented explicitly, so in order to use it, you should cast a POCO to this interface:
Patient p = new Patient() { };
IReadOnlyDictionary<string,object> patientDict = p;
As is clear from the generic parameters, the dictionary is keyed by a string: the element’s name. The dictionary will “contain” the key if the underlying element is not null, and (in the case of collection elements) has at least one item.
The value found for the key in the dictionary is of type object
, but can really only contain a restricted set of datatypes:
A .NET primitive type (string, int, long), a byte array or a DateTimeOffset
Another
IReadOnlyDictionary<string,object>
A collection of the types from the previous buttons
Resources and datatypes (like HumanName
, Identifier
, but also FhirString
and the backbone types like ContactComponent
in Patient
)
are represented using IReadOnlyDictionary
. You can navigate down the tree by using the indexer or the TryGetValue()
function of the interface.
Continuing the example above:
IReadOnlyDictionary<string,object> activeDict = patientDict["active"];
// since the underlying type is of course a FhirBoolean you could also do:
FhirBoolean active = (FhirBoolean)activeDict;
Note that, in contrast to the Json serialization of FHIR, resources will not contain additional magic resourceType entry in their dictionary.
Datatypes come in two flavours in FHIR: complex datatypes (and their sub-type, the anonymous backbone types like
Patient.ContactComponent
) and primitives. In FHIR, even primitives are complex since they not only
contain a primitive value, but also possibly an extension
and/or an id
. All datatypes are represented as a normal nested IReadOnlyDictionary
, but
the primitives have two additional features. First off, the dictionary for a FHIR primitive has a key value
that contain the actual .NET primitive
for the value of a FHIR primitive (see the table below for details). Additionally, these FHIR primitives all implement IFhirPrimitive
, an interface
that contains a property ObjectValue
of type object
. This property can be used to retrieve the .NET-based primitive value of the FHIR primitive as well:
object val1 = activeDict["value"];
object val2 = ((IFhirPrimitive)activeDict).ObjectValue;
Assert.AreEqual(val1,val2);
This is even true for narrative XML (found in each resource’s Text
property), which is represented using the XHtml
datatype in the dictionary.
even though Text.Div
is of type string in the POCO itself:
Narrative narrative = p.Text;
string xmlDiv1 = narrative.Div;
IReadOnlyDictionary<string,object> narrativeDict = narrative;
IReadOnlyDictionary<string,object> divDict = narrative["div"];
Assert.IsTrue(divDict is XHtml);
Assert.IsTrue(divDict is IFhirPrimitive);
object xmlDiv2 = divDict["value"];
object xmlDiv3 = ((IFhirPrimitive)divDict).ObjectValue;
Assert.AreEqual(xmlDiv1,xmlDiv2);
Assert.AreEqual(xmlDiv2,xmlDiv3);
This is in line with the definition of Narrative
in the specification,
but the way we generated the Narrative
datatype as a POCO slightly diverges from the specification, so this is worth pointing out.
The table below lists the exact .NET type used for the representation of primitives:
POCO Type |
.NET type |
---|---|
Base64Binary |
byte[] |
Code |
string |
Date/Time/FhirDateTime |
string |
FhirBoolean |
bool? |
FhirDecimal |
decimal? |
FhirString |
string |
FhirUri/FhirUrl |
string |
Id/Oid/Uuid/Canonical |
string |
Instant |
DateTimeOffset? |
Integer/PositiveInt/UnsignedInt |
int? |
Integer64 |
long? |
Markdown |
string |
XHtml (Narrative.Div) |
XHtml |
Code<T> |
string |
Choice elements are present in the dictionary both by their “normal” unsuffixed name (i.e. onset
for Condition.onset
, not onsetPeriod
for example).
Condition c = new Condition { OnSet = new FhirDateTime() };
IReadOnlyDictionary<string,object> conditionDict = c;
var onset1 = conditionDict["onset"];
Assert.IsTrue( onset1 is FhirDateTime );
Assert.IsTrue( conditionDict.ContainsKey("onset") );
Assert.IsFalse( conditionDict.ContainsKey("onsetDateTime") );
Assert.IsFalse( conditionDict.ContainsKey("onsetString") );
Since the classes for the resources and datatypes implement IReadOnlyDictionary<string,object>
they also implement IEnumerable<KeyValuePair<string,object>>
.
Similarly, this enumeration will contain the unsuffixed name for such choice elements.