Search code examples
jsonjqpathname

jq: convert array to object indexed by filename?


Using jq how can I convert an array into object indexed by filename, or read multiple files into one object indexed by their filename?

e.g.

jq -s 'map(select(.roles[]? | contains ("mysql")))' -C dir/file1.json dir/file2.json

This gives me the data I want, but I need to know which file they came from.

So instead of

[
    { "roles": ["mysql"] },
    { "roles": ["mysql", "php"] }
]

for output, I want:

{
    "file1": { "roles": ["mysql"] },
    "file2": { "roles": ["mysql", "php"] }
}

I do want the ".json" file extension stripped too if possible, and just the basename (dir excluded).


Example

file1.json
{ "roles": ["mysql"] }
file2.json
{ "roles": ["mysql", "php"] }
file3.json
{ }

My real files obviously have other stuff in them too, but that should be enough for this example. file3 is simply to demonstrate "roles" is sometimes missing.

In other words: I'm trying to find files that contain "mysql" in their list of "roles". I need the filename and contents combined into one JSON object.


To simplify the problem further:

jq 'input_filename' f1 f2

Gives me all the filenames like I want, but I don't know how to combine them into one object or array.

Whereas,

jq -s 'map(input_filename)' f1 f2

Gives me the same filename repeated once for each file. e.g. [ "f1", "f1" ] instead of [ "f1", "f2" ]


Solution

  • If your jq has inputs (as does jq 1.5) then the task can be accomplished with just one invocation of jq. Also, it might be more efficient to use any than iterating over all the elements of .roles.

    The trick is to invoke jq with the -n option, e.g.

    jq -n '
      [inputs
       | select(.roles and any(.roles[]; contains("mysql")))
       | {(input_filename | gsub(".*/|\\.json$";"")): .}]
      | add' file*.json