jmespath

Replace one of values in array if condition is true, in JMESPATH


Here is JSON:

{
  "a": [
    "foo",
    "bar",
    "test"
  ]
}

The idea is to replace bar by bar_new, if bar is present in the array.

The desired output will be:

[
  "foo",
  "bar_new",
  "test"
] 

So, if we don't have bar in the input JSON, the output will be:

[
  "foo",
  "test"
]

I've tried to put it together by map() and join() functions, but join() function returns a string instead of an array.
I need exactly this array in the output.


Solution

  • Here is a solution, the only caveat is that it does not replace the element bar, it removes it out of the array and appends an element bar_new if there was an element bar in the first place.
    So, effectively the element bar_new is not guaranteed to be at the same index as bar was.


    In JMESPath you cannot do an if ... else ..., but you can apply the same principle as you will sometimes find in shell scripts where we can do

    some_instruction \
      && another_instruction_when_the_first_one_is_successful \
      || yet_another_instruction_when_the_first_one_fail
    

    So, in your case you can start by saying:

    Finding an element in an array is quite simple, given that this is the role of the function contains().

    Giving you a query like:

    (contains(a, `bar`) && [`new_array_here`]) || a
    

    Knowing how to append an element in an array is a tricky one, there is no easy concatenation between arrays in JMESPath, this said, you can create an array of arrays and flatten it.

    For example:

    [[`a`,`b`],`c`] | []
    

    Would result in

    [
      "a",
      "b",
      "c"
    ]
    

    Combining all of this, the query

    (contains(a, `bar`) && [a[? @ != `bar`], `bar_new`] | []) || a
    

    Would yield, with your example JSON,

    [
      "foo",
      "test",
      "bar_new"
    ]