FHIR Chat · Finding index of element using FP? · fhirpath

Stream: fhirpath

Topic: Finding index of element using FP?


view this post on Zulip Patrick Narkinsky (Jun 07 2021 at 12:28):

Hi there. We've been working on a fhirpatch implementation for Javascript (which we're open sourcing). We're using the fhirpath.js module to handle the fhirpath's. In order to implement the delete operator for a path like this:

Practitioner.telecom.where(value='5551212')

Our current implementation tries to extract the index of the matching list entry. However, we've tried a couple of different options and neither is producing the desired result:

1) Practitioner.telecom.where(value='5551212').$index - returns the length of the array
2) Practitioner.telecom.where(value='5551212').select($index) - seems to return the number of matching elements in the array minus 1 (i.e. the index of the last matching element in the reduced list.)

Is there any robust way in FHIRPath to implement this? This is critical functionality for our use of fhirpatch, and we're trying to avoid having to write our own fhirpath implementation if we possibly can.

view this post on Zulip Grahame Grieve (Jun 07 2021 at 14:33):

@Bryn Rhodes I don't believe we've made any definition for $index outside an iterator, but it's use here would be very difficult, since the index would be the index in the .where() not the index in .telecom.

I think we'd have to define an indexOf() function on a list, and it seems like an obvious oversight to me

view this post on Zulip Grahame Grieve (Jun 07 2021 at 14:34):

in the meantime, patrick you can define your own additional functions no problem

view this post on Zulip Paul Lynch (Jun 07 2021 at 14:38):

Why do you want the index? What are you planning to use it for?

view this post on Zulip Patrick Narkinsky (Jun 07 2021 at 18:12):

Paul Lynch said:

Why do you want the index? What are you planning to use it for?

I started to try to explain this, but I think the source for our delete operation is clearer than any English explanation.

From https://github.com/caremesh/fhirpatch/blob/main/src/operation.js

case 'delete':
        // if the tail path contains an operation, patch it to be an absolute index
        if (this.tail.path.startsWith('where\(')) {
          const [idx] = fp.evaluate(resource, `${this.path}.$index`);
          if (typeof idx === 'undefined') {
            break;
          }
          this.path = `${this.containingPath}[${idx}]`;
        }

        res = fp.evaluate(resource, this.containingPath);

        if (res[0]) {
          // it is not an error if a path to delete doesn't already exist
          if (this.tail.index) {
            if (res.length == 0) {
              throw new PathNotFoundError(
                  `Attempt to modify index but path ${this.containingPath
                  } is not array`);
            }

            res[0][this.tail.path].splice(this.tail.index, 1);
          } else {
            delete res[0][this.tail.path];
          }
        }
        break;

Granted this is a bit of a hack, but the idea here is that we need to delete the path referenced by the patch from the container. So, we use fhirpath to get the parent (by tokenizing the path into the containingPath and the tail with an optional index) then we remove the referenced element from the parent. However, our whole use case for this requires us to be able to delete accurately when the list order changes (long story why) so we can't do something like delete Practitioner.telecom[0] -- we need to be able to delete by value.

That's why we decided to implement fhirpatch rather than try to use jsonpatch -- with jsonpatch there was just no apparent way to support this use case.

view this post on Zulip Patrick Narkinsky (Jun 07 2021 at 18:17):

Grahame Grieve said:

Bryn Rhodes I don't believe we've made any definition for $index outside an iterator, but it's use here would be very difficult, since the index would be the index in the .where() not the index in .telecom.

I think we'd have to define an indexOf() function on a list, and it seems like an obvious oversight to me

@Grahame Grieve am I correct in understanding that implementing the indexOf function would best be done within our fhirpath implementation (fhirpath.js)? Or is there some magic I'm not aware of?

view this post on Zulip Grahame Grieve (Jun 07 2021 at 18:33):

oh you'd have to do it in your implementation - there's no magic like getting an implementation for free ;-)

view this post on Zulip Paul Lynch (Jun 07 2021 at 18:33):

It seems like support for the FHIRPatch operations should be in FHIRPath.

view this post on Zulip Paul Lynch (Jun 07 2021 at 18:37):

@Patrick Narkinsky You can fork fhirpath.js and add the indexOf function, which I guess should take an expression. Alternativly, you could add a delete() function. Simultaneously, I would recommend filing a ticket on jira.hl7.org to add the missing functionality to the specification.

view this post on Zulip Patrick Narkinsky (Jun 07 2021 at 18:38):

@Paul Lynch since I've had no experience with ANTLR, I was trying to avoid getting deep into fhirpath. Oh well...

view this post on Zulip Paul Lynch (Jun 07 2021 at 18:42):

@Patrick Narkinsky I will PM you with some advice about the code-- you should not need to know ANTLR.

view this post on Zulip Patrick Narkinsky (Jun 07 2021 at 21:17):

Thanks folks! With @Paul Lynch 's help, I've got a working implementation. I'm going to clean it up and then offer a pull request. I will probably also significantly refactor our fhirpatch library now that I've seen how easy it is to modify fhirpath.js.

view this post on Zulip Paul Lynch (Jun 07 2021 at 21:40):

@Grahame Grieve Where you thinking indexOf would take an expression, like "where", as in Patient.name.indexOf(use='official') ?

view this post on Zulip Grahame Grieve (Jun 07 2021 at 21:49):

For me. more like this:

Patient.name.indexOf(Patient.name.where(use='official'))

view this post on Zulip Grahame Grieve (Jun 07 2021 at 21:49):

there's obvious problems with that

view this post on Zulip Paul Lynch (Jun 07 2021 at 21:50):

If you just put the expression into the indexOf, the function can be the same as "where" except it returns the index instead of the object.

view this post on Zulip Grahame Grieve (Jun 07 2021 at 21:51):

maybe but it isn't it less re-usable?

view this post on Zulip Paul Lynch (Jun 07 2021 at 21:52):

I'm not following. Re-usable in what sense?

view this post on Zulip Paul Lynch (Jun 07 2021 at 21:55):

Oh, I see. You want to be able to look up the index based on the object, rather than the expression that finds it. I suppose, but is twice the work if you are starting with an expression (first you find the object, and then you search for it again to find the index).

view this post on Zulip Grahame Grieve (Jun 07 2021 at 21:55):

look up the index based on the object

yes.

view this post on Zulip Paul Lynch (Jun 07 2021 at 22:01):

Testing for equality could be really inefficient if you have a complex object. (Bundle.entry.indexOf(...)).

view this post on Zulip Paul Lynch (Jun 07 2021 at 22:04):

It is too bad we can't pass in either an object or an expression. Maybe we need two functions.

view this post on Zulip Grahame Grieve (Jun 07 2021 at 22:10):

It is too bad we can't pass in either an object or an expression

can't we?

view this post on Zulip Paul Lynch (Jun 07 2021 at 23:06):

I don't think we currently have such a case. fhirpath.js would not handle it, but could be modified if the grammar could support it.

view this post on Zulip Paul Lynch (Jun 07 2021 at 23:46):

It might be possible to check whether what is passed into indexOf is a TermExpression (from the grammar-- this would mean an object or value is being passed in).

view this post on Zulip Grahame Grieve (Jun 08 2021 at 01:59):

well we haven't said that couldn't happen, but we do differentiate between iterators and everything else

view this post on Zulip Bryn Rhodes (Jun 08 2021 at 18:49):

Hmm... yes, definitely should have an IndexOf and agree it is an oversight. We could do it with the $index though:

Practitioner.telecom.select(iff(value='5551212', $index, { }))

view this post on Zulip Bryn Rhodes (Jun 08 2021 at 18:53):

Curious if that would work in fhirpath.js Paul?

view this post on Zulip Bryn Rhodes (Jun 08 2021 at 18:53):

https://jira.hl7.org/browse/FHIR-32882

view this post on Zulip Bryn Rhodes (Jun 08 2021 at 18:53):

And a tracker to remedy the oversight.

view this post on Zulip Paul Lynch (Jun 08 2021 at 20:47):

Bryn Rhodes said:

Curious if that would work in fhirpath.js Paul?

Yes, it does!

view this post on Zulip Grahame Grieve (Jun 14 2021 at 07:58):

that works in the java implementation when I commit it (I overlooked $index)


Last updated: Apr 12 2022 at 19:14 UTC