1

Is it possible to filter an entire array of items in JQ in only one pass? Compare the following code, which runs jq over and over:

{
      "foofoo": {
        "barbar": [
          {
            "foo": "aaaa",
            "bar": 0000
          },
          {
            "foo": "bbbb",
            "bar": 1111
          },
          {
            "foo": "cccc",
            "bar": 2222
          }
          ]
      }
}

bash array:

array=("1111" "2222")

my code is working but not very efficient and uses a lot of resources considering the array size in reality:

for k in "${array[@]}"; do 
    jq  --argjson k "$k"  '.foofoo.barbar |= map(select(.bar != $k))' json.json | sponge json.json 
done

It keeps looping through the array, removing the unneeded entries and storing same file again by using sponge.

any ideas how to achieve a similar behavior with a lighter code?

Desired output:

{
  "foofoo": {
    "barbar": [
      {
        "foo": "aaaa",
        "bar": 0
      }
    ]
  }
}
4
  • added desired output to question Commented Dec 22, 2018 at 15:42
  • @CharlesDuffy yes I mean without a shell loop Commented Dec 22, 2018 at 17:03
  • still, you won't get 0000 as output, it will be collapsed to "bar": 0 Commented Dec 22, 2018 at 17:27
  • yes it is an integer so 0 or 0000 will not affect the functionality Commented Dec 22, 2018 at 17:46

3 Answers 3

2

To improve the performance significantly use the following jq approach (without any shell loops):

arr=("1111" "2222")
jq '($p | split(" ") | map(tonumber)) as $exclude 
    | .foofoo.barbar 
      |= map(select(.bar as $b 
                    | any($exclude[]; . == $b) | not))' \
    --arg p "${arr[*]}" file.json | sponge file.json

The output:

{
  "foofoo": {
    "barbar": [
      {
        "foo": "aaaa",
        "bar": 0
      }
    ]
  }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Please check the last edit as .bar value is unquoted in the JSON file
@ma7555 That's not valid JSON; strings must be in quotes. Try it yourself: jq . <<<'{"bar": aaaa}' fails with an "invalid numeric literal" error. So does python -m json.tool <<<'{"bar": aaaa}', to show you that that's not just a jq limitation.
@CharlesDuffy it is actually an integer, I resembled it as a string just as an example. I might have caused a confusion with this
minimal reproducible example guidelines require that an example be "verifiable" -- it must not cause errors unrelated to the actual question.
@ma7555, you need to post the actual input avoiding invalid samples
|
1

I'm positive there are better ways to do this: I really just throw stuff at jq until something sticks to the wall ...

# 1. in the shell, construct a JSON object string from the array => {"bbbb":1,"cccc":1}
printf -v jsonobj '{%s}' "$(printf '"%q":1\n' "${array[@]}" | paste -sd,)"

# 2. use that to test for non-membership in the jq select function
jq --argjson o "$jsonobj" '.foofoo.barbar |= map(select((.bar|in($o)) == false))' json.json

outputs

{
  "foofoo": {
    "barbar": [
      {
        "foo": "0000",
        "bar": "aaaa"
      }
    ]
  }
}

You don't actually show your desired output, so I assume this is what you want.

1 Comment

One might also use map(select($o[.bar]!=1)) :)
0

Constructing a dictionary object opens the door to an efficient solution. If your jq has INDEX/2, you could use the following invocation:

jq --arg p "${arr[*]}" '
  INDEX($p | split(" ")[]; .) as $dict
  | .foofoo.barbar 
      |= map(select($dict[.bar|tostring] | not))' 

If your jq does not have INDEX/2, then now would be an excellent time to upgrade; otherwise, you could snarf its def by googling: jq "def INDEX"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.