Add quotes only around strings, not numbers or lists - json

My input JSON is of the format:
{
"a": "apple",
"b": "banana",
"c": 5,
"d": ["this", "is", "an", "array"],
"e": false
}
What I want is:
a="apple"
b="banana"
c=5
d=["this", "is", "an", "array"]
e=false
Note that only strings in the input JSON have quotes in the output.
By using jq -r 'to_entries[] | "\(.key)=\"\(.value)\""' I could generate an output like
a="apple"
b="banana"
c="5"
d="["this", "is", "an", "array"]"
e="false"
So my question is:
Is using jq the right way to approach this problem? Or should I use regex?
If jq is the correct direction, how do I fix what I've come up with?

You can test whether .value is a string or not using type (manual entry).
jq -r 'to_entries[]
| "\(.key) = \( .value
| if type == "string"
then "\"\(.)\""
else .
end
)"'

Related

Extract a particular field from json output using jq

I'm working a bash script to extraxt specific field from json output using jq.
USERNAME=$(echo "$OUTPUT" | jq -r '.[] | .name')
Due to jq it always fails with parse error: Invalid numeric literal at line 1, column 2 error.
My restapi result has the below output.
[
{
"url": "#/systemadm/groups/uuid-d6e4e05",
"options": {},
"group_id": 313,
"owner": "abc-123-mec",
"owner_id": "ad1337884",
"id": "c258d7b330",
"name": "abc-group"
},
{
"options": {},
"id": "global%3Regmebers",
"name": "Udata-123"
},
{
"url": "#/systemadm/groups/uuid-38943000",
"options": {},
"group_id": 910,
"owner": "framework-abcc",
"owner_id": "78d4472b738bc",
"id": "38943000057a",
"name": "def-group"
},
........................
............................
......................................
So what's wrong with this jq response of code to get "name" ?
jq can only process valid JSON.
If the value of OUTPUT is literally "id": "c258d7b330","name": "abc-group", then you could enclose it in curly braces to make it valid JSON. No guarantees though; this depends on the exact format of your input.
OUTPUT='"id": "c258d7b330",
"name": "abc-group"'
USERNAME="$(printf '%s\n' "{$OUTPUT}" | jq -r '.name')"
printf '%s\n' "$USERNAME"; // abc-group
If it cannot be converted to valid JSON, maybe a simple solution using grep+cut or awk would suffice?
OUTPUT='"id": "c258d7b330",
"name": "abc-group"'
USERNAME="$(printf '%s\n' "$OUTPUT" | grep '^"name":' | cut -d'"' -f4)"
awk:
printf '%s\n' "$OUTPUT" | awk -F'"' '/^"name":/{print $4}'
Or even use jq to parse the input as array of strings and then filter for the line in which you are interested:
jq -Rr '(select(startswith("\"name\":")) / "\"")[3]'
All options are really fragile and I recommend to fix your input to be actual, valid JSON

How to create keys in json file using jq?

I am trying to update a json file using jq. My json file looks like
{
"A": "123",
"B": "456",
"C": "789",
"D": []
}
Here the value for key D is empty so I am adding some values to it. And this is working
Now, if for some reason the key doesn't exist then I need to first create the key D. And I am not able to achieve this
{
"A": "123",
"B": "456",
"C": "789",
}
cat test.json | jq 'has("D")' = false && cat test.json jq --argjson addobj '{"D": "[]"}'
I am getting the error
jq: error: Could not open file =: No such file or directory
jq: error: Could not open file false: No such file or directory
expected output
{
"A": "123",
"B": "456",
"C": "789",
"D": []
}
Can anyone please let me know what is the issue here and how to resolve it?
Thanks in advance.
P.S: Please let me know if any info is missing here
Your problem is not (only) with jq, but with shell syntax.
But if all you are trying to do is to update the value of key D whether it exists or not, then you don't need any checks and can simply assign the new value:
$ jq '.D = ["new value"]' test.json
{
"A": "123",
"B": "456",
"C": "789",
"D": [
"new value"
]
}
If you want to modify the current value, the operator |= might be helpful.
An alternative, equivalent program would be '. + { D: ["new value"] }'
If you really want to fix your script, here's a working version of it:
if jq -e 'has("D")' test.json >/dev/null; then
# key exists
jq --argjson addobj '{"D": "[]"}' 'your program here' test.json
else
# key doesn't exist
jq 'your other program here'
fi
But this is arguably easier in jq directly:
jq --argjson addobj '{"D": "[]"}' '
if has("D") then
# D exists
. # <- your jq program
else
# D doesn't exist
. # <- your other jq program
end
' test.json
If your goal is to simply insert the key with a default value if it doesn't exist, but keep any existing value, the following simple jq program (and nothing else) should take care of that:
jq '{D: []} + .' test.json
(keys in the RHS overwrite keys from the LHS – {a:1}+{a:2} becomes {a:2})
Objects in JavaScript (and by extension JSON), are a bag of unordered key-value pairs and {a:1,b:2} is the same object as {b:2,a:1}.
However, jq mostly keeps order of keys (although I don't think this is specified/guaranteed). So, a slightly more complicated version which puts D at the end of the object, but keeps existing values would be:
jq '.D |= (. // [])' test.json

How to group a JSON by a key and sort by its count?

I start from a jsonlines file similar to this
{ "kw": "foo", "age": 1}
{ "kw": "foo", "age": 1}
{ "kw": "foo", "age": 1}
{ "kw": "bar", "age": 1}
{ "kw": "bar", "age": 1}
Please note each line is a valid json, but the whole file is not.
The output I'm seeking is an ordered list of keywords sorted by its occurrence. Like this:
[
{"kw": "foo", "count": 3},
{"kw": "bar", "count": 2}
]
I'm able to group and count the keywords using the slurp option
jq --slurp '. | group_by(.kw) | .[] | {kw: .[0].kw, count: . | length }'
Output:
{"kw":"bar","count":2}
{"kw":"foo","count":3}
But:
This is not sorted
This is not valid JSON array
A very stupid solution I've found, is to pass twice via jq :)
jq --slurp --compact-output '. | group_by(.kw) | .[] | {kw: .[0].kw, count: . | length }' sample.json \
| jq --slurp --compact-output '. | sort_by(.count)'
But I'm pretty sure someone smarter than me can find a more elegant solution.
This is not sorted
That is not quite correct, group_by(.foo) internally does a sort(.foo), so the results are shown in the sorted order of the field. See jq Manual - group_by(path_expression)
This is not valid JSON array
Just enclose the operation within [..] and also the leading . is optional. So just do
jq --slurp --compact-output '[ group_by(.kw)[] | {kw: .[0].kw, count: length } ]'
If you are referring to sort by the .count you can do a ascending sort and reverse
jq --slurp --compact-output '[ group_by(.kw)[] | {kw: .[0].kw, count: length }] | sort_by(.count) | reverse'

Linux command to print all jsons of same key

I have json as a string "Str"
"{
"A": {
"id": 4
},
"B": {//Something},
"C": {
"A": {
"id": 2
}
},
"E": {
"A": null
},
"F": {//Something}
}"
I wanted all non null values of "A" which can be repeated anywhere in json. I wanted output like all contents of "A"
{"id": 4}
{"id": 2}
Can you please help me with Linux command to get this ?
Instead of line oriented ones use a tool which is capable of parsing JSON values syntax wise. An example using jq:
$ json_value='{"A":{"id":4},"B":{"foo":0},"C":{"A":{"id":2}},"E":{"A":null},"F":{"foo":0}}'
$
$ jq -c '..|objects|.A//empty' <<< "$json_value"
{"id":4}
{"id":2}
.. # list nodes recursively
| objects # select objects
| .A // empty # print A's value if present.

Json parsing on cli using jq

Let's say I have the below json object:
{
"d": {
"e": {
"bar": 2
}
},
"a": {
"b": {
"c": {
"foo": 1
}
}
}
}
I want to get the value foo without typing '.a.b.c.foo'
I realize I can do...
echo '{ "a":{"b":{"c":{ "foo":1}}},"d":{"e":{"bar":2}}}' | jq '.[][][].foo' but is there a recursive wild in jq? like **? I know for sure jq doesn't support *, is there a way to have jq support jsonpath?
Or maybe even just another cli tool that does support json path?
In jq 1.4 you could do this:
$ jq '..|.foo?' file.json
If you're stuck with 1.3 you could use
$ jq 'recurse(if type == "array" or type == "object" then .[] else empty end) | if type == "object" then . else empty end | .foo' file.json
which is a bit of a mouthful... That's why 1.4 has .., which recurses down through all iterables in ., and the ? operator, which doesn't bother indexing that which can't be.