Skip to content

Commit dc9aefe

Browse files
Merge pull request #54 in LFOR/fhirpath.js from bugfix/LF-2635/hashObject-for-null-values to master
* commit '0c5fd085b741455de562fc37d0d477d4241e8c29': npm audit fix Resolved merge conflicts Fixed a typo Fixed issues with null values Reverted unnecessary changes and did minor refactoring Added a benchmark for the "contains" operator Changes as per review Added unit tests for the hashObject function Fix hash for null values #138
2 parents 0e1c4ed + 0c5fd08 commit dc9aefe

15 files changed

Lines changed: 300 additions & 23 deletions

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
This log documents significant changes for each release. This project follows
44
[Semantic Versioning](http://semver.org/).
55

6+
## [3.9.1] - 2024-01-22
7+
### Fixed
8+
- Fixed exception in the "hashObject" internal function when an object has
9+
a property with the "null" value. This may affect functions that compare
10+
objects:
11+
intersect(), subsetOf(), repeat(), union(), distinct(), isDistinct().
12+
- Null values are excluded from the expression evaluation result.
13+
- Fixed an issue when evaluating an expression for a resource object with missed
14+
values at the end of the array in a property.
15+
- Fixed an issue when evaluating an expression for a resource object when there
16+
are no values at all for a property, but there is a list of associated data
17+
(ids/extensions).
18+
619
## [3.9.0] - 2023-11-09
720
### Added
821
- support for user-defined functions.

demo/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fhirpath",
3-
"version": "3.9.0",
3+
"version": "3.9.1",
44
"description": "A FHIRPath engine",
55
"main": "src/fhirpath.js",
66
"dependencies": {
@@ -55,5 +55,5 @@
5555
"fhirpath": "bin/fhirpath"
5656
},
5757
"repository": "github:HL7/fhirpath.js",
58-
"license": "MIT"
58+
"license": "SEE LICENSE in LICENSE.md"
5959
}

src/collections.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ const { deepEqual } = require('./deep-equal');
55
const engine = {};
66

77

8-
// b is assumed to have one element and it tests whether b[0] is in a
8+
// "b" is assumed to have one element and it tests whether "b[0]" is in "a"
99
function containsImpl(a,b){
10-
if(b.length == 0) { return true; }
1110
for(var i = 0; i < a.length; i++){
1211
if(deepEqual(a[i], b[0])) { return true; }
1312
}

src/fhirpath.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,18 @@ engine.MemberInvocation = function(ctx, parentData, node ) {
372372
if(Array.isArray(toAdd)) {
373373
acc = acc.concat(toAdd.map((x, i)=>
374374
makeResNode(x, childPath, _toAdd && _toAdd[i])));
375+
// Add items to the end of the ResourceNode list that have no value
376+
// but have associated data, such as extensions or ids.
377+
const _toAddLength = _toAdd?.length || 0;
378+
for (let i = toAdd.length; i < _toAddLength; ++i) {
379+
acc.push(makeResNode(null, childPath, _toAdd[i]));
380+
}
381+
} else if (toAdd == null && Array.isArray(_toAdd)) {
382+
// Add items to the end of the ResourceNode list when there are no
383+
// values at all, but there is a list of associated data, such as
384+
// extensions or ids.
385+
acc = acc.concat(_toAdd.map((x) =>
386+
makeResNode(null, childPath, x)));
375387
} else {
376388
acc.push(makeResNode(toAdd, childPath, _toAdd));
377389
}
@@ -680,7 +692,7 @@ function applyParsedPath(resource, parsedPath, context, model, options) {
680692
// Resolve any internal "ResourceNode" instances to plain objects and if
681693
// options.resolveInternalTypes is true, resolve any internal "FP_Type"
682694
// instances to strings.
683-
.map(n => {
695+
.reduce((acc,n) => {
684696
// Path for the data extracted from the resource.
685697
let path = n instanceof ResourceNode ? n.path : null;
686698
n = util.valData(n);
@@ -689,13 +701,17 @@ function applyParsedPath(resource, parsedPath, context, model, options) {
689701
n = n.toString();
690702
}
691703
}
692-
// Add a hidden (non-enumerable) property with the path to the data extracted
693-
// from the resource.
694-
if (path && typeof n === 'object') {
695-
Object.defineProperty(n, '__path__', {value: path});
704+
// Exclude nulls
705+
if (n != null) {
706+
// Add a hidden (non-enumerable) property with the path to the data extracted
707+
// from the resource.
708+
if (path && typeof n === 'object') {
709+
Object.defineProperty(n, '__path__', {value: path});
710+
}
711+
acc.push(n);
696712
}
697-
return n;
698-
});
713+
return acc;
714+
}, []);
699715
}
700716

701717
/**

src/hash-object.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ function hashObject(obj) {
2424
*/
2525
function prepareObject(value) {
2626
value = valDataConverted(value);
27-
28-
if (typeof value === 'number') {
27+
if (value === null) {
28+
return null;
29+
} else if (typeof value === 'number') {
2930
return roundToMaxPrecision(value);
3031
} else if (value instanceof Date) {
3132
return value.toISOString();

test/benchmark.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ child.on('exit', code => {
4444
'union',
4545
'exclude',
4646
'subsetof',
47-
'distinct'
47+
'distinct',
48+
'contains'
4849
].forEach(filename => {
4950
const suites = require('./benchmark/'+ filename)({
5051
benny,

test/benchmark/contains.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const _ = require('lodash');
2+
3+
module.exports = ({
4+
current_fhirpath,
5+
current_r4_model,
6+
bigItems,
7+
smallItems
8+
}) => {
9+
10+
const lastOfBigItemsCopy = _.cloneDeep(bigItems.slice(-1));
11+
const numberOfBigItems = current_fhirpath.evaluate({}, '%items.count()', { items: bigItems }, current_r4_model)[0];
12+
13+
const lastOfSmallItemsCopy = _.cloneDeep(smallItems.slice(-1));
14+
const numberOfSmallItems = current_fhirpath.evaluate({}, '%items.count()', { items: smallItems }, current_r4_model)[0];
15+
16+
const expression = '%items contains %lastOfItemsCopy';
17+
18+
return [{
19+
name: `Checking if a list of items contains some item`,
20+
filename: 'contains',
21+
expression,
22+
cases: [
23+
{
24+
name: `${numberOfBigItems} big items using evaluate()`,
25+
testFunction: (fhirpath, model) => {
26+
fhirpath.evaluate({}, expression, { items: bigItems, lastOfItemsCopy: lastOfBigItemsCopy }, model);
27+
}
28+
},
29+
{
30+
name: `${numberOfBigItems} big items using compile()`,
31+
testFunction: (fhirpath, model, compiledFn) => {
32+
compiledFn({}, { items: bigItems, lastOfItemsCopy: lastOfBigItemsCopy });
33+
}
34+
},
35+
{
36+
name: `${numberOfSmallItems} small items using evaluate()`,
37+
testFunction: (fhirpath, model) => {
38+
fhirpath.evaluate({}, expression, { items: smallItems, lastOfItemsCopy: lastOfSmallItemsCopy }, model);
39+
}
40+
},
41+
{
42+
name: `${numberOfSmallItems} small items using compile()`,
43+
testFunction: (fhirpath, model, compiledFn) => {
44+
compiledFn({}, { items: smallItems, lastOfItemsCopy: lastOfSmallItemsCopy });
45+
}
46+
}
47+
]
48+
}];
49+
50+
}

test/cases/5.1_existence.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,15 @@ tests:
354354
- prop1: 5
355355
prop2: 6
356356

357+
- desc: '** should ignore null properties'
358+
inputfile: patient-bundle.json
359+
model: r4
360+
expression: >-
361+
Bundle.entry.distinct() = (Bundle.entry[0] | Bundle.entry[2])
362+
result:
363+
- true
364+
365+
357366
- desc: '** should use year-to-month conversion factor (https://hl7.org/fhirpath/#equals)'
358367
expression: (1 year).combine(12 months).distinct()
359368
result:

0 commit comments

Comments
 (0)