Search code examples
jsonata

JSONata: Remove first occurrence of item in array


I want to remove the first occurrence of an element in an array in JSONata.

I tried this:

(
    $largerArray := ["a","b","c","a","b","c"];
    $itemToExclude := "a";
    $expectedArray := ["b","c","a","b","c"];
    
    $doesNotWorkBecauseVariablesAreLocal := function($array, $element){(
        $found := false;
        $filter($array, function($v){(
            $isElem := $v = $element;
            $shouldDrop := $isElem and $not($found);
            $found := $found or $isElem;
            $not($shouldDrop)
        )})
    )};

    $doesNotWorkBecauseVariablesAreLocal($largerArray,"a");

    $reduced := $reduce($largerArray, function($acc,$arrayItem){(
        $array := $lookup($acc, "array");
        $foundAlready := $lookup($acc,"foundAlready");
        $itemToExclude := $lookup($acc,"itemToExclude");
        $shouldExclude := $arrayItem = $itemToExclude and $not($foundAlready);
        $foundAlready := $foundAlready or $shouldExclude;
        $shouldExclude ? {"array":$array, "foundAlready":$foundAlready, "itemToExclude":$itemToExclude} : {"array":$append($array,$arrayItem), "foundAlready":$foundAlready, "itemToExclude":$itemToExclude}
    )}, {"array":[], "foundAlready":false, "itemToExclude":$itemToExclude});

    $reduced.array;
)

doesNotWorkBecauseVariablesAreLocal does not work because the found variable is locally scoped. So I guess the first part of my question is: could this kind of approach be made to work?

The fact that it didn't seem to work made me try the second approach, of carrying state through an object that contains the array, the foundAlready boolean and the itemToExclude

It works, but it isn't elegant.

What is the idiomatic JSONata way to pull this off?


Solution

  • You can use $reduce (https://docs.jsonata.org/higher-order-functions#reduce) to find the first occurrence of an item. $reduce iterates over an array and passes an accumulator (the first index) as well as the current index and value. We can initialize the accumulator to -1 and if it is still -1 and the value matches the search, we return the index as the new accumulator value.

    Once we have the index, we can use $filter (https://docs.jsonata.org/higher-order-functions#filter) to remove that index from the array:

    (
        $largerArray := ["a","b","c","a","b","c"];
        $itemToExclude := "b";
        $expectedArray := ["b","c","a","b","c"];
        
    
        $dropFirst := function($array,$item){(
            $index := $reduce($array, function($accumulator, $value, $index){ $value = $item and $accumulator = -1 ? $index : $accumulator }, -1 );
            $filter($array, function($v, $i){$i != $index ? $v})
        )};
    
        $dropFirst($largerArray, $itemToExclude)
    )