Search code examples
yamlyq

How to select YAML mapping keys if they contain special keys


I have a big YAMl file and i need to select all top mapping keys, that have other mapping key under them. It would be great if i could done parsing with yq, but other solutions are welcome too For example, i have yaml file

foo:
  bar:
    - "hello"
quz:
  bar:
    - "hi"
der:
  boo: true

and i need to select just foo and bar so my final yaml will look like this:

foo:
  bar:
    - "hello"
quz:
  bar:
    - "hi"

I already tried something like this, it is do nothing test.yaml contains example above

yq eval 'select(.[] | has("bar"))' test.yml
yq eval 'select(.[] | .bar != null)' test.yml

And i generally do not understand why using query without select gives me correct boolean output

yq eval '.[] | has("bar")' test.yml

true
true
false

Solution

  • With .[] | has("bar") you get the booleans you would use in a wrapping select expression, but you're missing that .[] destructures the outer map (to generate the booleans) while select is still applied to the whole map (as it's in the outer context).

    One way to achieve what you want is using with_entries, which takes a function that is provided access to each .key and .value, but retains the outer structure:

    yq 'with_entries(select(.value | has("bar")))'
    

    Another way could be to use the pick function, which takes a list of keys to keep. Generate that list by applying your original filter, and extracting each key from the matches (while using map instead of .[] to retain the array):

    yq 'pick(map(select(has("bar")) | key))'
    

    Both output:

    foo:
      bar:
        - "hello"
    quz:
      bar:
        - "hi"
    

    Tested with mikefarah/yq version v4.35.1. Note that since version v4.18.1, the eval/e command is the default, and can be omitted. (As an aside, the with_entries approach should also work as is using kislyuk/yq.)