JSON Node Path Syntax¶
JavaScript Object Notation (JSON) is a lightweight, text-based data interchange format. A JSON node path references a key, value, or array element in a JSON message. JSON node paths use forward-slash separated keys, with array elements addressed by 1-based index in brackets (for example /phoneNumbers/[1]).
Node paths in this section can be used with getNode, getAllNodes, and other QIE functions that accept a nodePath parameter. See How Node Paths Resolve for behavior details and the Node Path Lookup Dialog to build and validate them interactively against a sample message.
The JSON message below is used with the JSON node path examples that follow:
{
"firstName": "Don",
"lastName": "Bassett",
"deathIndicator": "",
"birthDate": "06/15/2001",
"address": {
"streetAddress": "375 5th Avenue",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"usage": "home",
"number": "212 555-1234"
},
{
"usage": "office",
"number": "646 555-4567"
},
{
"usage": "mobile",
"number": "123 456-7890"
}
]
}
| JSON Node Path | Description |
|---|---|
/ |
Returns the entire JSON message |
/firstName |
Returns the value: Don |
/address |
Returns the entire address object: { "streetAddress": "375 5th Avenue", "city": "New York", "state": "NY", "postalCode": "10021-3100" } |
/address/streetAddress |
Returns the value of streetAddress: 375 5th Avenue |
/phoneNumbers |
Returns the entire phoneNumbers array |
/phoneNumbers/[1] |
Returns the 1st phone number object: { "usage": "home", "number": "212 555-1234" } |
/phoneNumbers/[2] |
Returns the 2nd phone number object: { "usage": "office", "number": "646 555-4567" } |
/phoneNumbers/[1]/number |
Returns the first phone number: 212 555-1234 |
/phoneNumbers/[usage="home"] |
Selects the phone object where usage equals "home": { "usage": "home", "number": "212 555-1234" } |
/phoneNumbers/[usage="home"]/number |
Selects the home phone number: 212 555-1234 |
Where the Bracket Goes¶
The bracket carries two different meanings in a JSON node path, and the slash in front of it decides which one applies.
| Node Path | Selects |
|---|---|
/phoneNumbers/[1] |
The 1st element of the phoneNumbers array |
/phoneNumbers[1] |
The 1st node named phoneNumbers, which is the array itself |
/phoneNumbers[2] |
Nothing: the message has no second phoneNumbers key |
/phoneNumbers/number[2] |
The 2nd number across all elements: 646 555-4567 |
An empty key before the bracket makes it an array index. A key name before the bracket makes it an instance number, counting the nodes that match that key.
The instance form is the one described in How Node Paths Resolve, where OBX[2]-5 and getNode('OBX-5', 2) are equivalent for HL7. JSON keeps that meaning for a key that repeats across array elements, and adds the /[n] form for the elements themselves.
JSON null Values¶
A key whose value is null is a node that exists and holds no value. getNode returns null for it rather than an empty string. Given this message:
source.getNode('/phoneNumbers/[1]/extension'); // null
source.getNode('/phoneNumbers/[1]/pager'); // "" because there is no pager key
The difference matters when the result is used directly: calling a string method on the first result fails, because there is no string to call it on. StringUtils accepts a null argument and checkNodeIsBlank reports true for both cases, so either one is safe without testing the value first.
if (StringUtils.isBlank(source.getNode('/phoneNumbers/[1]/extension'))) {
// true for a JSON null and for a missing key
}
checkNodeExists is what separates the two: it reports true for a key set to null and false for a key that is absent.
Counting Elements¶
getCount(nodePath) returns the number of elements in the JSON array the path resolves to, whether or not the path ends with the /[] array filter. A path that descends through the array to a repeated child key returns one node per element; a path to a single element or a scalar value returns 1.
| JSON Node Path | getCount Returns |
|---|---|
/phoneNumbers |
3, the number of elements in the array |
/phoneNumbers/[] |
3, the number of elements in the array |
/phoneNumbers/number |
3, one number per element |
/phoneNumbers/[1] |
1, the addressed element |
/firstName |
1, a single scalar value |
Prefer the /[] form when counting array elements. A bare array path such as /phoneNumbers behaves differently across functions: getCount returns the element count, while getNode returns the whole array as a single value. Only the /[] form addresses the individual elements, for example getNode('/phoneNumbers/[1]') or getNode('/phoneNumbers/[]', i) in a loop. Writing the count as getCount('/phoneNumbers/[]') keeps one consistent path meaning across getCount, getNode, and getJSONArrayCount.
Filter Operators and Predicates¶
JSON segment filters support the same predicate functions (contains, starts-with, ends-with, equals) as HL7 HPath. See Filter Operators and Predicates under the HL7 section for full descriptions and function signatures. The optional caseSensitive argument defaults to false (case-insensitive).
The examples below use the JSON sample message shown above.
| Function | Example | Returns |
|---|---|---|
contains |
/phoneNumbers/[contains(usage, 'om')]/number |
The number of the first phone whose usage contains om (matches home): 212 555-1234 |
starts-with |
/phoneNumbers/[starts-with(number, '212')]/usage |
The usage of the first phone whose number starts with 212: home |
ends-with |
/phoneNumbers/[ends-with(number, '4567')]/usage |
The usage of the first phone whose number ends with 4567: office |
equals |
/phoneNumbers/[equals(usage, 'OFFICE')]/number |
The number of the first phone whose usage equals OFFICE (case-insensitive default matches office): 646 555-4567 |
For nested JSON structures, the predicate body may also use XPath-style relative references such as ../<name> to access a sibling field of the parent context, or ..//<name> to match any descendant of the parent context. For example, /customfields/id[contains(../id,'14')] would match each id whose parent's id contains '14'.
Writing to Arrays¶
The same bracket syntax addresses array elements on a write, with one added rule: an indexed write may target at most one element past the end of the array. setNode creates whatever is missing along the path, so with the three phone numbers in the sample message above, /phoneNumbers/[4]/usage appends a fourth object and sets usage on it. Skipping ahead to /phoneNumbers/[5]/usage throws a MessageModelException:
JsonMessageModel: Node not found for index: 5 for array /phoneNumbers/[5] in node path: /phoneNumbers/[5]/usage
That error almost always means the index came from the wrong counter. A loop that reads one value and writes one array element has two numbers in play: the position being read from the input, and the number of elements written so far. Only the second one is a valid array index. Keep it in its own variable and increment it once per element written.
var numbers = StringUtils.splitByWholeSeparator(source.getNode('/phones'), ';');
var rowIndex = 1;
for (var i = 0; i < numbers.length; i++) {
if (StringUtils.isBlank(numbers[i])) {
continue; // i advances, rowIndex does not
}
message.setNode('/phoneNumbers/[' + rowIndex + ']/number', StringUtils.trim(numbers[i]));
rowIndex++;
}
An empty bracket pair does not append. /phoneNumbers/[]/number writes the first matching element, exactly as it does on a read. To append without tracking an index at all, use addObjectToJSONArray: it creates the array on its first call and adds one element per call. getJSONArrayCount returns the number of elements currently in the array.
Note
Mixing the two forms in one loop is what leaves empty objects in the array. addObjectToJSONArray has already made room, so the index to write is the array's new last position, not one beyond it. See Converting Repeating HL7 Segments to a JSON Array for the append pattern in full.
Value Types on a Write¶
The type QIE writes follows the type of the value handed to it, not the way the value looks.
| Value written | Result in the message |
|---|---|
| A string, whatever it contains | A JSON string: '93000' writes "93000", and 'true' writes "true" |
| A JavaScript number | A JSON number, always with a decimal point: 93000 writes 93000.0 |
| A JavaScript boolean | A JSON boolean |
| A string that parses as a JSON object or array | That object or array, not a string |
Node paths read as strings, so a value taken from getNode and written with setNode keeps its string type. A numeric-looking identifier such as an MRN or a CPT code stays a string with its leading zeros intact, and needs no special handling.
Writing an actual number is where the type needs stating. A JavaScript number literal reaches QIE as a double, so setNode and setJSONNumber both write 93000.0. Use setJSONInteger for a whole number without the decimal point. setJSONNumber accepts a numeric string and converts it, which is how a value read from a message becomes a real JSON number.
message.setNode('/code', '93000'); // "93000"
message.setNode('/count', 5); // 5.0
message.setJSONNumber('/count', '5'); // 5.0
message.setJSONInteger('/count', 5); // 5
Removing Array Elements¶
removeFirstNode, removeLastNode, and removeAllNodes accept the same paths as a read. Use the /[n] form to remove a single element:
Leaving the index off removes the array itself, key and all:
Put the bracket after the slash
/phoneNumbers[1] does not address the first element. The bracket counts nodes named phoneNumbers, so the path resolves to the array itself. At the top level of the message, removeFirstNode and removeLastNode throw JsonMessageModel: Node not found: /phoneNumbers[1], and removeAllNodes does nothing. Below the top level, all three remove the entire array.