Juve Juve - 11 months ago 58
JSON Question

jq: Testing if a key is in a list of predefined keys

I have a case where I need to parse quoted JSON in JSON.
I know which optional attributes will contain quoted JSON and which not.
Therefore, I want to check if the attribute keys are in a list of possible keys. I already have the following:

# attributes "a" and "b" contain quoted JSON
echo '{"a":"{\"x\":1}","y":2}' |
jq -c '
def is_json($o): ["a","b"] | (map(select(. == $o)) | length) > 0;
with_entries(if is_json(.key) then .value = (.value|fromjson) else . end)

This already produces the desired output:
. However, the checking of the attribute name looks clumsy, given that
provides a lot built-in functions such as
, etc.

Question: Is there a better way of checking if an attribute key is in a given list?

Edit: Here is the current solution, based on peak's answer.

to_array_string() { echo "$*" | awk -v OFS='","' 'NF > 0 {$1=$1; print "\""$0"\""}'; }
to_json_array_string() { echo "["`to_array_string "$@"`"]"; }

parse_json_jq() { jq -c "
reduce keys[] as \$key
(.; if any((`to_array_string "$@"`); . == \$key) and .[\$key] != null then .[\$key] |= fromjson else . end)

Answer Source

There are three ways in which your program can be improved:

  1. (efficiency) avoiding the creation of an unnecessary array (in is_json);
  2. (efficiency) using "short-circuit" semantics to avoid iterating unnecessarily;
  3. (efficiency) avoid the construction/deconstruction involved with with_entries;

For the most part, I think you will agree that the alternatives offered here are simpler, more concise, or more readable.

If you have version 1.5 of jq or later, the main improvements can be had using any/2:

def is_json($o): any( ("a","b"); . == $o );

with_entries(if is_json(.key) then .value |= fromjson else . end)

Notice also the use of '|=' where you had used '='.

If your jq does not have any/2, then you could use the following definition, though it lacks short-circuit semantics:

def any(s): reduce s as $i (false; . == true or $i);

Finally, to avoid using with_entries, you could use reduce and eliminate is_json entirely:

reduce keys[] as $key
  (.; if any(("a","b"); . == $key) then .[$key] |= fromjson else . end)