Said in the voice of Gollum.
That's how I feel after spending a couple of days generating a FHIR JSON to XML converter in JavaScript. I'm in the middle of creating a FHIR to CDA converter, and I happened to have some FHIR example objects that were nearly correct in JSON that I needed to clean up, but not in XML. I suppose I could have figured out how to do this in HAPI or some other such tool, but I thought I'd look into this bit of a programming challenge as a way to learn more about FHIR serialization formats.
My first problem was that I started this project somewhere around 1 in the morning early Sunday. Why? Because I was bored I think, but also inspired by Grahame's ask in the FHIR Skype chat if he could generate anything useful from the FHIR data to help in such a conversion. I realized that I could generate the necessary data from the FHIR Schemas. I pretty quickly cobbled together an XSLT transform (Yes, I XSLT is for some reason, one of my favorite languages to code in) that operated on the FHIR Schemas. It generates a JavaScript Object from the Schema for each simple or complex Schema type defined therein. Here's the definition for Basic:
"Basic": {
/*Basic is used for handling concepts not yet defined in FHIR, narrative-only resources that don't map to an existing resource, and custom resources not appropriate for inclusion in the FHIR specification.If the element is present, it must have either a @value, an @id, or extensions
*/
"name": "Basic",
"base": "DomainResource",
"attrs": [
],
"elems": [
{ /*Identifier assigned to the resource for business purposes, outside the context of FHIR. */
"name": "identifier", "min": "0", "max": "unbounded","type": "Identifier",
"isPrimitive": false },
{ /*Identifies the 'type' of resource - equivalent to the resource name for other resources.*/ "name": "code", "min": "1", "max": "1", "type": "CodeableConcept", "isPrimitive": false },
{ /* Identifies the patient, practitioner, device or any other resource that is the "focus" */
* of this resource. */
"name": "subject", "min": "0", "max": "1", "type": "Reference", "isPrimitive": false },
{ /*Indicates who was responsible for creating the resource instance. */
"name": "author", "min": "0", "max": "1", "type": "Reference","isPrimitive": false },
{ /*Identifies when the resource was first created. */
"name": "created", "min": "0", "max": "1", "type": "date","isPrimitive": true },
]
},
Pretty gross, huh? It gets a bit uglier. In the above, identifier points to a pretty long type, which eventually references IdentifierUse, which eventually references IdentifierUse-List.
"Identifier": {
/*A technical identifier - identifies some entity uniquely and unambiguously.If the element is present, it must have a value for at least one of the defined elements, an @id referenced from the Narrative, or extensions
*/
"name": "Identifier", "base": "Element",
"attrs": [ ],
"elems": [ {
/*The purpose of this identifier.*/
"name": "use", "min": "0", "max": "1", "type": "IdentifierUse", "isPrimitive": true },
... elided for readability ...
]
},
"IdentifierUse-list": {
"name": "IdentifierUse-list",
"base": "xs:string",
"values": [ "usual", "official","temp","secondary",]
},
"IdentifierUse": {
/*Identifies the purpose for this identifier, if knownIf the element is present, it
* must have either a @value, an @id, or extensions
*/
"name": "IdentifierUse", "base": "Element",
"attrs": [ { "name": "value", "type": "IdentifierUse-list" }, ],
"elems": [ ]
},
By drilling down through the types, you can eventually determine that the "use" property of the Identifier type is a primitive type. But it takes a while. Rather than try to figure this out at run-time, I precompute the isPrimitive property from the FHIR Schema. Anything that derives from Element and doesn't have any elements of its own I treat as a FHIR primitive. As it turns out, this precomputation of isPrimitive meant that all the work I did for simpleTypes in XML Schema was pretty much wasted. Except that it might be useful later.
Processing the JSON gets a bit tricky as well. The JSON output for elements that can have cardinality > 1 is an array, but when cardinality is limited to at most one, its a FHIR object. However, the output of each of the elements in the array is exactly the same. So I created a function to process these objects, and if there is only one object and it isn't an array, I make it an array. I also have to deal with the extra gunge for a property. This is the stuff that, for property X, gets stored in property _X.
It took me quite a while to get this right because I had mutually recursive functions in JavaScript. And my JavaScript chops are a bit rusty, so I forgot to declare a couple of variables, which made them globals, and that made them not have the value they were supposed to after the recursion. It took me a while to figure all this out, and I still haven't figured out everything I need to do with the extra bits stored in extensions on primitive types. But, what I have works closely enough for now (but needs a lot more testing). I'm fixing to put it over in GitHub at some point once I get some more free time, so that folks who want to convert FHIR from JSON to XML can.
Oh, and the JavaScript object that I generate also has all the necessary data to go from XML to JSON as well. That will have to wait for another time. And, you could actually use it to validate the JSON. Again, another time.
Keith