switch case in JQ while creating new object - jq

I would like to use IF THEN in my JQ query while creating a new object but I'm getting an error
.customer | {"company": .companyName, "email": .email, "phone": .phone,
"type": if .customerType.name == "A" then "SOHO" else "Other" end
}
jq: error: syntax error, unexpected if (Unix shell quoting issues?) at
, line 2:
"type": if .customerType.name == "A" then "SOHO" else "Other" end jq: 1 compile error exit status 3

You have to parenthesize the value expression:
.customer | {
"company": .companyName,
email,
phone,
"type": (if .customerType.name == "A" then "SOHO" else "Other" end)
}

Related

Conditionally output a field?

In this example I only want isGreaterThanOne field to be shown if it's true. Here's what I started with (always shown)
echo '[{"a":5},{"a":1}]' | jq '[.[] | {value:.a, isGreaterThanOne:(.a>1)}]'
I inserted an if statement
echo '[{"a":5},{"a":1}]' | jq '[.[] | {value:.a, X:(if .a>1 then "Y" else "N" end) }]'
Then got stuck trying to move the field into the conditional. Also it seems like I must have an else with an if
echo '[{"a":5},{"a":1}]' | jq '[.[] | {value:.a, (if .a>1 then (K:"Y)" else (L:"N") end) }]'
I want the below as the result (doesn't need to be pretty printed)
[
{
"value": 5,
"X": "Y"
},
{
"value": 1,
}
]
Using if, make one branch provide an empty object {} which wouldn't contain the extra field:
map({value: .a} + if .a > 1 then {X: "Y"} else {} end)
Demo
Alternatively, equip only selected items with the extra field:
map({value: .a} | select(.value > 1).X = "Y")
Demo
Output:
[
{
"value": 5,
"X": "Y"
},
{
"value": 1
}
]

jq: list users belonging to a specific group in array

input json:
[
{
"user": "u1"
},
{
"user": "u2",
"groups": [
{
"id": "100001",
"name": "G1"
},
{
"id": "100002",
"name": "G2"
}
]
},
{
"user": "u3",
"groups": [
{
"id": "100001",
"name": "G1"
}
]
}
]
I want to find all users belonging to specific group (searching by group name or group id in the groups array)
$ jq -r '.[]|select(.groups[].name=="G1" | .user)' json
jq: error (at json:27): Cannot iterate over null (null)
Desired output format when searching of example group G1 would be:
u2
u3
Additional question:
Is it possible to produce comma-separated output u2,u3 without using external utilities like tr?
Better enter your serach data from parameters using --arg and use any to avoid duplicate outputs if both inputs match:
jq -r --arg id "" --arg name "G1" '
.[] | select(.groups | map(.id == $id or .name == $name) | any)? | .user
'
u2
u3
Demo
Using ? as the Optional Object Identifier-Index operator, you could do a select as below
map(select(.groups[].name == "G1")? | .user)
and un-wrap the results from the array by using [] at the end of the filter. To combine multiple selection conditions use the boolean operators with and/or inside the select statement
See demo on jqplay

How to abbreviate/truncate the value of every key?

I have a JSON like:
{
"a": "hi",
"b": 4213,
"c": 23154646789132456,
"d": "a very long string that should be shortened",
"e": {
"x": "some value",
"y": {
"alpha": "foo"
}
}
}
I would like to use jq to "abbreviate" values longer than n characters. Each value should be converted to a string s, and if the result is longer than n, it should be replaced with s[:n] + " ...". If the string is not too long, it should ideally be left as the original value. I expect a result like:
# n=5
{
"a": "hi",
"b": 4213, # "4213" would be acceptable too, but not preferable
"c": "23154 ...",
"d" : "a ver ...,
"e" : "{\n\"x\" ..." # I don't care how whitespace is handled
}
# yes, I know there's no comments in JSON :)
The idea is to have something like "folding" when looking at complex objects, so that I can get an overview of what top level keys are there, and then decide which key I want to "zoom in" to (eg jq '.e').
In Python I could do something like:
j = load_my_json()
for k in j:
s = str(j[k])
if len(s) > n:
j[k] = f"{s[:10]}..."
But how can I do it in jq?
After some trial and error, I came up with:
$ cat j.json | jq 'to_entries | map({(.key): (.value | tostring[:20] + " ...")})' | jq -s '.[] | add'
{
"a": "shortval",
"b": "4213",
"c": "23154646789132456",
"d": "a very long string t",
"e": "{\"x\":\"some value\",\"y"
}
Which is what I want. This seems a bit inelegant, especially having to do the -s call, but it's good enough for what I'm doing. I'll leave the question unanswered in case someone has a better solution.
In the simplest case, we convert each item using tostring and check the string's length
$ jq --argjson n 10 '.[] |= (
tostring | if length > $n then .[:$n] + "..." else . end
)' j.json
{
"a": "hi",
"b": "4213",
"c": "2315464678...",
"d": "a very lon...",
"e": "{\"x\":\"some..."
}
To prevent the numbers from being converted to strings, we can wrap the simple case with another if condition checking the item's type
$ jq --argjson n 10 '.[] |= (
if type == "number" then . else
tostring | if length > $n then .[:$n] + "..." else . end
end
)' j.json
{
"a": "hi",
"b": 4213,
"c": 23154646789132456,
"d": "a very lon...",
"e": "{\"x\":\"some..."
}
Finally, if we want to keep the numbers as numbers if and only if their string representation matches the length criteria, we serialize both conditions with both applying tostring once for conversion and once for testing:
$ jq --argjson n 10 '.[] |= (
if type == "number" then . else tostring end
| if tostring | length > $n then tostring | .[:$n] + "..." else . end
)' j.json
{
"a": "hi",
"b": 4213,
"c": "2315464678...",
"d": "a very lon...",
"e": "{\"x\":\"some..."
}

Extract nested properties from an array of objects

I have the following JSON file :
{
"filter": [
{
"id": "id_1",
"criteria": {
"from": "mail#domain1.com",
"subject": "subject_1"
},
"action": {
"addLabelIds": [
"Label_id_1"
],
"removeLabelIds": [
"INBOX",
"SPAM"
]
}
},
{
"id": "id_2",
"criteria": {
"from": "mail#domain2.com",
"subject": "subject_1"
},
"action": {
"addLabelIds": [
"Label_id_2"
],
"removeLabelIds": [
"INBOX",
"SPAM"
]
}
}
]
}
And I would like to extract emails values : mail#domain1.com and mail#domain2.com
I have tried this command:
jq --raw-output '.filter[] | select(.criteria.from | test("mail"; "i")) | .id'
But does not work, I get this error :
jq: error (at <stdin>:1206): null (null) cannot be matched, as it is
not a string exit status 5
Another point : how to display the value of "id" key, where "from" key value = mail#domain1.com ?
So in my file id = id_1
Do you have an idea ?
If you only need to extract the emails from .criteria.from then this filter is enough as far as I can tell:
jq --raw-output '.filter[].criteria.from' file.json
If some objects don't have a criteria object then you can filter out nulls with:
jq --raw-output '.filter[].criteria.from | select(. != null)' file.json
If you want to keep the emails equal to "mail#domain1.com":
jq --raw-output '.filter[].criteria.from | select(. == "mail#domain1.com")' file.json
If you want to keep the emails that start with "mail#":
jq --raw-output '.filter[].criteria.from | select(. != null) | select(startswith("mail#"))' file.json
I would like to extract emails values
There is a wide spectrum of possible answers, with these
amongst the least specific with respect to where in the JSON the email addresses occur:
.. | objects | .from | select(type=="string")
.. | strings | select(test("#([a-z0-9]+[.])+[a-z]+$"))

regex replacement for whole object tree / reverse operation to `tostring`

So I have big json, where I need to take some subtree and copy it to other place, but with some properties updated (a lot of them). So for example:
{
"items": [
{ "id": 1, "other": "abc"},
{ "id": 2, "other": "def"},
{ "id": 3, "other": "ghi"}
]
}
and say, that i'd like to duplicate record having id == 2, and replace char e in other field with char x using regex. That could go (I'm sure there is a better way, but I'm beginner) something like:
jq '.items |= . + [.[]|select (.id == 2) as $orig | .id=4 | .other=($orig.other | sub("e";"x"))]'<sample.json
producing
{
"items": [
{
"id": 1,
"other": "abc"
},
{
"id": 2,
"other": "def"
},
{
"id": 3,
"other": "ghi"
},
{
"id": 4,
"other": "dxf"
}
]
}
Now that's great. But suppose, that there ins't just one other field. There are multitude of them, and over deep tree. Well I can issue multiple sub operations, but assuming, that replacement pattern is sufficiently selective, maybe we can turn the whole JSON subtree to string (trivial, tostring method) and replace all occurences using singe sub call. But how to turn that substituted string back to — is it call object? — to be able to add it back to items array?
Here's a program that might be a solution to the general problem you are describing, but if not at least illustrates how problems of this type can be solved. Note in particular that there is no explicit reference to a field named "other", and that (thanks to walk) the update function is applied to all candidate JSON objects in the input.
def update($n):
if .items | length > 0
then ((.items[0]|keys_unsorted) - ["id"]) as $keys
| if ($keys | length) == 1
then $keys[0] as $key
| (.items|map(.id) | max + 1) as $newid
| .items |= . + [.[] | select(.id == $n) as $orig | .id=$newid | .[$key]=($orig[$key] | sub("e";"x"))]
else .
end
else .
end;
walk(if type == "object" and has("items") then update(2) else . end)

Resources