66

In jq, how can I convert a JSON to a string with key=value?

From:

{
    "var": 1,
    "foo": "bar",
    "x": "test"
}

To:

var=1
foo=bar
x=test
fedorqui
  • 275,237
  • 103
  • 548
  • 598
gianebao
  • 17,718
  • 3
  • 31
  • 40

6 Answers6

112

You could try:

jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' test.json

Here's a demo:

$ cat test.json
{
    "var": 1,
    "foo": "bar",
    "x": "test"
}
$ jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' test.json
foo=bar
var=1
x=test
Jakub Bochenski
  • 3,113
  • 4
  • 33
  • 61
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 3
    Is there any way i can do this recursively? – gianebao Aug 19 '14 at 07:50
  • There is a recurse function... But I guess you need to say what field to recurse on. Do you have a fixed field you want to recurse on, or just "any value that's an object"? – aioobe Aug 19 '14 at 07:52
  • none in particular. I'll just try a different approach for the recurse. – gianebao Aug 20 '14 at 01:13
  • I found this question looking to convert a json object into an array of key/value pairs -- `to_entries` is exactly what I needed. – mattsilver Mar 13 '17 at 15:05
  • @aioobe is there a way you could capitalize the keys? I tried `\(.key | tr a-z A-Z)` to no avail. – Dane Jordan Feb 11 '19 at 00:37
  • `jq -r 'to_entries|map("\(.key|@uri)=\(.value|@uri)")|join("&")'` gets you a valid query string. – cachius Sep 21 '21 at 10:34
  • To protect the values by quoting them (for use in shell scripts for example), use:`jq -r 'to_entries|map("\(.key)=" + @sh "\(.value|tostring)")|.[]'`. The `@sh` string formatter is available since `jq` version 1.3 . – ack Jan 24 '22 at 09:40
  • @aioobe, perhaps you might want to suggest `.value | @sh` as an available option to ensure that the results are safe to execute as a shell command. (That would also have the side effect of disambiguating if your value contains, say, newlines; you don't want `"foo": "bar\nbaz=qux"` to look like it's setting a `baz` key separate from the `foo` key) – Charles Duffy Jul 19 '23 at 13:36
  • @CharlesDuffy thanks for the suggestion, I have just done so and deleted my comment here (and will delete this one later, but I’ll leave it in for a while so you know (and can delete yours if you want, to avoid chattiness)). – mirabilos Jul 20 '23 at 01:14
5

Is there any way i can do this recursively?

Here is a function which might do what you want:

# Denote the input of recursively_reduce(f) by $in.
# Let f be a filter such that for any object o, (o|f) is an array.
# If $in is an object, then return $in|f;
# if $in is a scalar, then return [];
# otherwise, collect the results of applying recursively_reduce(f)
# to each item in $in.
def recursively_reduce(f):
  if type == "object" then f
  elif type == "array" then map( recursively_reduce(f) ) | add
  else []
  end;

Example: emit key=value pairs

def kv: to_entries | map("\(.key)=\(.value)");


[ {"a":1}, [[{"b":2, "c": 3}]] ] | recursively_reduce(kv)
#=> ["a=1","b=2","c=3"]

UPDATE: After the release of jq 1.5, walk/1 was added as a jq-defined built-in. It can be used with the above-defined kv, e.g. as follows:

 walk(if type == "object" then kv else . end) 

With the above input, the result would be:

[["a=1"],[[["b=2","c=3"]]]]

To "flatten" the output, flatten/0 can be used. Here is a complete example:

jq -cr 'def kv: to_entries | map("\(.key)=\(.value)");
        walk(if type == "object" then kv else . end) | flatten[]'

Input:

[ {"a":1}, [[{"b":2, "c": 3}]] ]

Output:

a=1
b=2
c=3
peak
  • 105,803
  • 17
  • 152
  • 177
5

Incidentally, building off of @aioobe's excellent answer. If you need the keys to be all upper case you can use ascii_upcase to do this by modifying his example:

jq -r 'to_entries|map("\(.key|ascii_upcase)=\(.value|tostring)")|.[]'

Example

I had a scenario similar to yours but wanted to uppercase all the keys when creating environment variables for accessing AWS.

$ okta-credential_process arn:aws:iam::1234567890123:role/myRole | \
     jq -r 'to_entries|map("\(.key|ascii_upcase)=\(.value|tostring)")|.[]'
EXPIRATION=2019-08-30T16:46:55.307014Z
VERSION=1
SESSIONTOKEN=ABcdEFghIJ....
ACCESSKEYID=ABCDEFGHIJ.....
SECRETACCESSKEY=AbCdEfGhI.....

References

slm
  • 15,396
  • 12
  • 109
  • 124
  • All-uppercase is an _incredibly_ bad idea; that's where security-sensitive things like `LD_LIBRARY_PATH` and `PATH` and `LD_PRELOAD` live. You should be ensuring that untrusted variables have _lowercase_ names so they can't break your OS. See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html ("the namespace of environment variable names containing lowercase letters is reserved for applications"). – Charles Duffy Jul 19 '23 at 13:32
2

without jq, I was able to export every item in json using grep and sed but this will work for simple cases only where we have key/value pairs

for keyval in $(grep -E '": [^\{]' fileName.json | sed -e 's/: /=/' -e "s/\(\,\)$//"); do
    echo "$keyval"
done

here's a sample response:

❯ for keyval in $(grep -E '": [^\{]' config.dev.json | sed -e 's/: /=/' -e "s/\(\,\)$//"); do
    echo "$keyval"       
done
"env"="dev"
"memory"=128
"role"=""
"region"="us-east-1"
Muhammad Soliman
  • 21,644
  • 6
  • 109
  • 75
1

To add on top of aaiobe’s answer, here’s a few things to make it safer / more flexible to use:

  • question mark for null safety
  • support for one level of nested arrays
  • jsn_ præfix for namespacing the resulting variables to avoid them overwriting your normal variables
$ cat nil.jsn
null
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <nil.jsn
$ cat test.jsn
{
  "var": 1,
  "foo": [
    "bar",
    "baz",
    "space test"
  ],
  "x": "test"
}
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <nil.jsn
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn
set -A jsn_'var' -- 1
set -A jsn_'foo' -- 'bar' 'baz' 'space test'
set -A jsn_'x' -- 'test'

The above outputs Korn Shell arrays (for mksh and AT&T ksh). If you prefer/need GNU bash-style arrays (also supported by many other recent shell releases) use:

$ jq -r 'to_entries? | map("jsn_\(.key | @sh)=(\(.value | @sh))") | .[]' <nil.jsn
$ jq -r 'to_entries? | map("jsn_\(.key | @sh)=(\(.value | @sh))") | .[]' <test.jsn
jsn_'var'=(1)
jsn_'foo'=('bar' 'baz' 'space test')
jsn_'x'=('test')

In all subsequent examples I will be using the Korn Shell variant, but applying the difference between the above two variants will work for them.

In both cases, you get arrays, but dereferencing the array itself is the same as dereferencing its element #0 so this is safe even for single values:

$ eval "$(jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn)"
$ echo $jsn_x
test
$ echo $jsn_foo = ${jsn_foo[0]} but not ${jsn_foo[1]}
bar = bar but not baz

There is a downside though: scripts written for just sh do not have arrays, so you need to either target bash, ksh88, ksh93, mksh, zsh, (possibly others), or their common subset (all of these support [[ … ]]; all but ksh88 should support GNU bash-style arrays).


Two further improvements:

  • manual null handling (see below)
  • filter the top-level keys from a whitelist
$ jq -r 'if . == null then
>         null
> else
>         to_entries | map(
>                 select(IN(.key;
>                         "foo", "val", "var"
>                 )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
>         ) | .[]
> end' <nil.jsn
null
$ jq -r 'if . == null then
>         null
> else
>         to_entries | map(
>                 select(IN(.key;
>                         "foo", "val", "var"
>                 )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
>         ) | .[]
> end' <test.jsn
set -A jsn_'var' -- 1
set -A jsn_'foo' -- 'bar' 'baz' 'space test'

In this example, only the keys foo, val (not existent here) and var are permitted. This can be used e.g. to filter out keys whose values are something other than simple values or one-dimensional JSONArrays of simple values, so the result is safe¹.

You would use this e.g. as follows in a shell snippet:

set -o pipefail
if ! vars=$(curl "${curlopts[@]}" "$url" | jq -r '
        if . == null then
                null
        else
                to_entries | map(
                        select(IN(.key;
                                "foo", "val", "var"
                        )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
                ) | .[]
        end
    '); then
        echo >&2 "E: no API response"
        exit 1
fi
if [[ $vars = null ]]; then
        echo >&2 "E: empty API response"
        exit 1
fi
eval "$vars"
echo "I: API response: var=$jsn_var"
for x in "${jsn_foo[@]}"; do
        echo "N: got foo '$x'"
done

① While jq throws an error unless the question mark is used, the failure mode is not so nice if there are indeed multi-dimensional arrays or other shenanigans:

$ cat test.jsn 
{
  "foo": [
    [
      "bar",
      "baz"
    ],
    "space test"
  ],
  "var": 1,
  "x": "test"
}
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn
$ echo $?
0
$ jq -r 'to_entries | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn 
jq: error (at <stdin>:11): array (["bar","baz"]) can not be escaped for shell
$ echo $?
5
$ jq -r 'if . == null then
>         null
> else
>         to_entries | map(
>                 select(IN(.key;
>                         "foo", "val", "var"
>                 )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
>         ) | .[]
> end' <test.jsn
jq: error (at <stdin>:11): array (["bar","baz"]) can not be escaped for shell
$ echo $?
5

Make sure to test for that if you don’t know your JSON will be fine. Heck, test for that, always; catch jq error exit codes. Note how the set -o pipefail in the above example, if supported by the shell (most recent ones do), makes the whole command substitution fail if either cURL or jq fail (or both, of course); otherwise you’d have to redirect cURL output to a temporary file, check curl’s errorlevel, then run jq on the temporary file in a command substitution and check its exit status (you’ll still have to do precisely that if you wish to distinguish between them in error messages).

mirabilos
  • 5,123
  • 2
  • 46
  • 72
  • Unfortunately, bash doesn't accept `jsn_'var'=(1)` as an assignment with the quotes on the left side. Might need to check the variable name against POSIX rules (first character a letter, remaining characters alphanumeric or underscores) before allowing the output. – Charles Duffy Jul 20 '23 at 02:40
  • BTW, one approach I'm often fond of is emitting a stream of `key=val` sequences -- in jq, a NUL can be represented as `"\u0000"`; using `jq -j` suppresses automatic newlines, so you can add your own separators between items, and then have a loop like (in bash) `declare -A vars=(); while IFS= read -r -d '' item; do [[ $item = *=* ]] || continue; vars[${item%%=*}]=${item#*=}; done < <(jq ...)` to populate an array. (Of course, to be properly paranoid one wants to make sure neither key nor value contains a NUL, and the key doesn't contain an `=`; so there's still some validation). – Charles Duffy Jul 20 '23 at 02:44
  • oh, this is fun… I’ve been using only the ksh syntax and didn’t check the bash syntax, and it indeed does not permit quoted LHS @CharlesDuffy so, yeah, whitelist approach for that │ as to your NUL-separated approach, would be best to make a separate answer for that as it’s again separate from _this_ approach (can be made to work on Korn shells without the `<(jq …)` bashism using coprocesses) – mirabilos Jul 24 '23 at 21:22
0

Here's a compact solution: to_entries[]|join("=")

$ echo '{"var": 1, "foo": "bar", "x": "test"}' | \
jq -r 'to_entries[]|join("=")'
var=1
foo=bar
x=test
wjordan
  • 19,770
  • 3
  • 85
  • 98