jq | get the paths value after filtered paths

Viewed 54

Let's say I have the following JSON:

{
  "A": {
    "ID": "something"
  },
  "B": {
    "ID": "something-else"
  },
  "C": {
    "ID": "something"
  },
  "D": {
    "E": {
      "ID": "something"
    }
  }
}

and I want to get all the paths that contain "ID": "something":

A.ID
C.ID
D.E.ID

to try to do it I'm using this to get all the paths that have the ID key

paths | select(.[-1] == "ID")

but the output is:

[ "A", "ID" ]
[ "B", "ID" ]
[ "C", "ID" ]
[ "D", "E", "ID" ]

jq playground

3 Answers

It should be possible to do with getpath/1. Collect the paths matching your condition and extract the value at each path to see if matches your condition

( paths | select(.[-1] == "ID")) as $p | 
  if getpath($p) == "something" then ( $p | join(".") ) else empty end

jqplay demo

Another approach would be using the --stream option (or the tostream function), and check both criteria at once:

jq -r --stream 'select(.[0][-1] == "ID" and .[1] == "something")[0] | join(".")'
A.ID
C.ID
D.E.ID

Demo

A more generic solution is a script I've written called jqg that will flatten the JSON and then search through it. It's not a perfect fit because it cannot search both the keys for one string and the values for another in a single invocation, but that's easily accomplished by stringing together two invocations with a pipe (|). On the plus side, you don't have to rewrite it every time your search criteria changes.

/tmp/so72977106.json - sample JSON with a bunch of extra edge cases thrown in

{
  "A": {
    "ID": "something"
  },
  "B": {
    "ID": "something-else"
  },
  "C": {
    "ID": "something"
  },
  "C2": {
    "ID": {
      "name": "something"
    }
  },
  "C3": {
    "name": "ID"
  },
  "C4": {
    "XID": "something"
  },
  "C5": {
    "ID": "not-something"
  },
  "D": {
    "E": {
      "ID": "something"
    }
  }
}
$ jqg -k '\.ID$' /tmp/so72977106.json | jqg -vKr ^something$
A.ID
C.ID
D.E.ID

The first invocation searches just the keys (-k) for the regex \.ID$ and the second invocation searches just the values (-v) for the regex ^something$. The output of the second invocation is modified to print out just the raw (-r), flattened keys (-K); otherwise it would produce valid JSON, e.g. the output of just the first invocation:

$ jqg -k '\.ID$' /tmp/so72977106.json
{
  "A.ID": "something",
  "B.ID": "something-else",
  "C.ID": "something",
  "C5.ID": "not-something",
  "D.E.ID": "something"
}

NOTE: I am the author of this script.

Related