How to combine update with function result in jq? - json

Given data like this:
cat << EOF > xyz.json
[
{
"batch_id": 526,
"aCods": [
"IBDD879"
]
},
{
"batch_id": 357,
"aCods": [
"IBDD212"
]
}
]
EOF
What is the correct way to get this result?
[
{
"batch_id": "00000526",
"aCods": [
"IBDD879"
]
},
{
"batch_id": "00000357",
"aCods": [
"IBDD212"
]
}
]
I have tried three different commands hoping to be able to update a object element in an array with the result of a function on that element.
I just cannot find the right syntax.
jq -r '.[] | .batch_id |= 9999999' xyz.json;
{
"batch_id": 9999999,
"aCods": [
"IBDD879"
]
}
{
"batch_id": 9999999,
"aCods": [
"IBDD212"
]
}
jq -r '.[] | lpad("\(.batch_id)";8;"0")' xyz.json;
00000526
00000357
jq -r '.[] | .batch_id |= lpad("\(.batch_id)";8;"0")' xyz.json;
jq: error (at /dev/shm/xyz.json:14): Cannot index number with string "batch_id"

Assuming you are trying to use the lpad/2 from this peak’s comment, you can do
def lpad($len; $fill): tostring | ($len - length) as $l | ($fill * $l)[:$l] + .;
map(.batch_id |= lpad(8; "0"))
The key here is when using the update assignment operator |= the field being modified is passed on internally, so that you don’t have to call it out explicitly in the RHS

Related

Using jq with 'contains' in a 'select' inside a 'del' is not working

i try to remove some entries from a dict in a json. It works by using == but with contains it doesn't work.
Jq call working:
jq 'del(.entries[] | select(.var == "foo"))' input.json
Jq call not working:
jq 'del(.entries[] | select(.var | contains("foo")))' input.json
input.json:
{
"entries": [
{
"name": "test1",
"var": "foo"
},
{
"name": "test2",
"var": "bar"
}
]
}
Output:
{
"entries": [
{
"name": "test2",
"var": "bar"
}
]
}
The result of jq '.entries[] | select(.var == "foo")' input.json and jq '.entries[] | select(.var | contains("foo"))' input.json is the same, so I think the two del-calls should also work.
Is this a bug in jq or did I something wrong?
This must be a bug as it seems to work perfectly on jq 1.6 (try it here).
If you're unable to update to jq 1.6 you should be able to use the following command instead, which I've successfully tested on jq 1.5 :
jq '.entries |= map(select(.var | contains("foo") | not))' file.json

Perform string manipulation on a value and return the original JSON document with jq

In my JSON document I have a string that I need manipulated and then have the entire document returned with the 'fixed' values.
The input document is:
{
"records" : [
{
"time": "123456789000"
},
{
"time": "123456789000"
}
]
}
I want to find the "time" key and replace the string by dropping off the last 3 chars. The resulting document would be:
{
"records" : [
{
"time": "123456789"
},
{
"time": "123456789"
}
]
}
I've been trying to understand the jq query syntax but I'm not coming right. I'm still struggling to return the whole document when filtering on a specific value. All I have so far is:
.records[] | select(.time | contains("123456789000"))
Here is a solution using |= and string slicing
.records[].time |= .[:-3]
Sample Run (assuming data in data.json)
$ jq -M '.records[].time |= .[:-3]' data.json
{
"records": [
{
"time": "123456789"
},
{
"time": "123456789"
}
]
}
Try it online at jqplay.org
With jq sub() function:
jq '.records[].time |= sub("[0-9]{3}$";"")' file
The output:
{
"records": [
{
"time": "123456789"
},
{
"time": "123456789"
}
]
}
Or even simpler: via dividing the time value by 1000:
jq '.records[].time |= (tonumber / 1000 | tostring)' file
The following works with jq version 1.4 or later:
jq '.records[].time |= .[:-3]' file.json
(The expression .[:-3] is short for .[0:-3]; the negative integer here counts from the right.)
With jq 1.3, the following filter would work in your particular case:
.records[].time |= (tonumber | ./1000 | tostring)

How to capture values from JSON parent and sub-parent parameter using JQ?

I have this below JSON file which I need to capture using JQ, but so far I only manage to capture the parent parameter (SUBSCRIBER_ID), but unable to capture the sub-parent parameter which is "Offer".
Need your guys help on providing a correct JQ filter to capture both "SUBSCRIBER_ID" and "Offer" value.
JSON
{"Data1": [
{"Data2": {
"SUBSCRIBER_ID" : "999050280010099",
"MSISDN" : "999050280010099",
"EMAIL" : "john#email.com",
"OFFERS" : [
{
"Offer" : 12344,
"EffectiveDate" : "1488787236",
"ExpiryDate" : "4070869200"
} ],
"IsGroup" : "false",
}}
]}
My JQ Filter which is not working
'.Data1 | .[] | .Data2 | to_entries | map(.value) | #csv' -r
Expected output:
SUBSCRIBER_ID,Offer
999050280010099,12344
You can try this jq:
jq -r '.Data1|.[]|.Data2|[.SUBSCRIBER_ID, .OFFERS[].Offer]|#csv' file > out.csv
(OR) As suggested by #peak,
jq -r '.Data1[].Data2|[.SUBSCRIBER_ID, .OFFERS[].Offer]|#csv' file
Another one method:
jq -r '.[]|.[]|map([.SUBSCRIBER_ID, .OFFERS[].Offer])|.[]|#csv' file
Input:
$ cat file.json
{
"Data1": [
{
"Data2": {
"SUBSCRIBER_ID": "999050280010099",
"MSISDN": "999050280010099",
"EMAIL": "john#email.com",
"OFFERS": [
{
"Offer": 12344,
"EffectiveDate": "1488787236",
"ExpiryDate": "4070869200"
}
],
"IsGroup": "false"
}
}
]
}
Test:
$ jq -r '.Data1|.[]|.Data2|[.SUBSCRIBER_ID, .OFFERS[].Offer]|#csv' file.json
"999050280010099",12344
$ jq -r '.[]|.[]|map([.SUBSCRIBER_ID, .OFFERS[].Offer])|.[]|#csv' file.json
"999050280010099",12344

Extracting element from array in JSON with jq returns "Cannot index array with string"

I have a JSON file:
$ cat ~/tmp/example1.json
[
{
"keyProp": 11111111111111,
"values": [
"VALUE1"
]
},
{
"keyProp": 2222,
"values": [
"VALUE2"
]
}
]
I want to use jq to select values where keyProp==11111111111111. Expected output is 'VALUE2'
I have tried but without result:
cat ~/tmp/example1.json | jq 'select(.keyProp==11111111111111)'
jq: error (at <stdin>:14): Cannot index array with string "keyProp"
To select a block you need to use the expression described in the docs:
select(boolean_expression)
The function select(foo) produces its input unchanged if foo returns
true for that input, and produces no output otherwise.
It’s useful for filtering lists: [1,2,3] | map(select(. >= 2)) will
give you [2,3].
jq '.[] | select(.id == "second")'
Input [{"id": "first", "val": 1}, {"id": "second", "val": 2}]
Output {"id": "second", "val": 2}
So in this case you need to say:
$ jq '.[] | select(.keyProp==11111111111111)' file
{
"values": [
"VALUE1"
],
"keyProp": 11111111111111
}
To extract the list in value, just say so:
$ jq '.[] | select(.keyProp==11111111111111).values' file
[
"VALUE1"
]
You can even extract the first value by using indexes:
$ jq '.[] | select(.keyProp==11111111111111).values[0]' file
"VALUE1"

Get the index of the array element in JSON with jq

I have the following type of json:
{
"foo": "hello",
"bar": [
{
"key": "k1",
"val": "v1"
},
{
"key": "k2",
"val": "v2"
},
{
"key": "k3",
"val": "v3"
}
]
}
I want to output the following:
"hello", 1, "k1", "v1"
"hello", 2, "k2", "v2"
"hello", 3, "k3", "v3"
I am using jq to tranform this and the answer should also be with a jq transformation.
I am currently at:
echo '{"foo": "hello","bar": [{"key": "k1","val": "v1"},{"key": "k2","val": "v2"},{"key": "k3","val": "v3"} ]}' | jq -c -r '.bar[] as $b | [.foo, ($b | .key, .val)] | #csv'
Which gives me:
"hello","k1","v1"
"hello","k2","v2"
"hello","k3","v3"
How can I also get the index to show of the array element being parsed?
You could convert the array to entries to access the index and the value. Then you can build out the CSV rows.
$ jq -r '[.foo] + (.bar | to_entries[] | [.key+1,.value.key,.value.val]) | #csv' input.json
"hello",1,"k1","v1"
"hello",2,"k2","v2"
"hello",3,"k3","v3"
Assuming you have access to jq 1.5 and that the key/val keys are presented in that order:
jq -r '.foo as $foo
| foreach .bar[] as $i (0; .+1; [$foo, .] + [$i[]])
| #csv'
would produce:
"hello",1,"k1","v1"
"hello",2,"k2","v2"
"hello",3,"k3","v3"
The -r option is often used with #csv to convert the JSON string that would otherwise be produced by #csv into a comma-separated list of values.
If you really want to join with ", ", then it's a bit messier, but if you're not worried about the functionality that #csv provides, here's one way:
$ jq -r '"\"\(.foo)\"" as $foo
| foreach .bar[] as $i
(0; .+1; "\($foo), \(.), \($i | map("\"\(.)\"")|join(", "))")'
This produces:
"hello", 1, "k1", "v1"
"hello", 2, "k2", "v2"
"hello", 3, "k3", "v3"
If your jq does not have foreach then you could similarly use reduce, but it might be easier to upgrade.