Conditionally print value based on the value of another key - json

Here's some example JSON:
{
"Tags": [
{
"Key": "Name",
"Value": "foo"
},
{
"Key": "Type",
"Value": "C"
}
]
}
I want to print the value of "Value" only when "Key" is "Type". So it should print out "C". This is what I have so far.
echo $MY_TAGS | jq 'if .Tags[].Key == "Type" then .Tags[].Value else empty end'
But it prints out:
"foo"
"C"
Is there a way to do this?

Try this:
.Tags[] | select(.Key == "Type") | .Value

Related

How to refer to parent object in jq walk?

I have some json that I want to add to based on a walk. A simple example of the json I have is below:
{
"valueSource": "memory",
"dataType": "Boolean",
"alarms": [
{
"setpointA": 1.0,
"name": "Alarm",
"priority": "Diagnostic",
"ackMode": "Auto",
}
],
"name": "Test Alarm",
"value": false,
"tagType": "AtomicTag"
}
I want to add to each object in the "alarms" key's array the following key:
{
"bindType": "Tag",
"value": "[.]<parent.name>.Name"
}
where <parent.name> is "Test Alarm" in this example which is the parent-of-the-alarm-array-item's "name" key.
I've gotten this jq filter so far that adds the object, but the value key value is wrong (it's getting the alarm array item's name instead of its parent's name):
walk( if type == "object" and .setpointA then .label = {"bindType":"Tag", "value": "[.]\(.name).Name"} else . end)
Essentially I want this:
{
"valueSource": "memory",
"dataType": "Boolean",
"alarms": [
{
"setpointA": 1.0,
"name": "Alarm",
"priority": "Diagnostic",
"ackMode": "Auto",
"label": {
"bindType": "Tag",
"value": "[.]Test Alarm.Name"
}
}
],
"name": "Test Alarm",
"value": false,
"tagType": "AtomicTag"
}
Here is my jqplay below. It has the final result in the JSON section, where the Result should match this but doesn't at the moment.
https://jqplay.org/s/-qHFIWolrD
Follow up question:
How would I add this displayPath key as well?
You cannot reference to parent, you have to save the reference in a variable beforehand, and descend with having access to that variable.
.tags[] |= (
.name as $name
| # rest of your code using $name
walk(
if type == "object" and .setpointA
then .label = {"bindType":"Tag", "value": "[.]\($name).Name"}
else . end
)
)
Demo
As you happen to know that the objects are located in the .alarms array, you could also just iterate over the items, select only those matching the condition and then assign to their .label whatever you want (including $name)
.tags[] |= (
.name as $name
| (.alarms[] | select(has("setpointA"))).label = {
bindType: "Tag", value: "[.]\($name).Name"
}
)
Demo
Edit responding to OP's follow-up question
I actually don't care about the setpointA key, I just want to add the label key (as well as another displayPath key) to each item in all alarms arrays.
.tags[] |= (.name as $name | .alarms[] += (
{bindType: "Tag", value: "[.]\($name)."} | {
label: (.value += "Name"),
displayPath: (.value += "Documentation")
}
))
Demo
As jq does not allow you to refer to parent object, you need to work on parent level :
jq 'walk(if type == "object" and .alarms and ( .alarms | arrays )
then (.alarms[] | select(.setpointA)).label =
{ bindType: "Tag", value: "[.]\(.name).Name"}
else . end
)' data.json

add a placeholder into all objects by specific key

I have an irregular json, e.g.:
[
{ "key": 123 },
[
{ "key": null },
{ "not key": "blah-blah" }
]
]
What's the most efficient way to add a placeholder record ("new key": null) into all objects which have "key" record? this is an expected output:
[
{ "key": 123, "new key": null },
[
{ "key": null, "new key": null },
{ "not key": "blah-blah" }
]
]
Using jq 1.6 and its walk function to modify JSON objects recursively :
jq 'walk(if type == "object" and has("key") then . + { "new key" : null } else . end)'
You can try it here.
The following will work on jq 1.5 :
jq 'def addNewKey: map_values(if type == "object" and has("key") then . + { "new key" : null } elif type=="array" or type=="object" then addNewKey else . end); addNewKey'
You can try it here.

JQ - how to display objects based on on the value of objects in an array

I have a JSON file that looks like this:
{
"InstanceId": "i-9KwoRGF6jbhYdZi823aE4qN",
"Tags": [
{
"Key": "blah",
"Value": "server-blah"
},
{
"Key": "environment",
"Value": "ops"
},
{
"Key": "server_role",
"Value": "appserver"
},
{
"Key": "Name",
"Value": "some_name"
},
{
"Key": "product",
"Value": "some_server"
}
]
}
{
...more objects like the above...
}
I need to display the InstanceId where "Key" == "environment" and "Value" == "ops".
I have jq-1.6.
If I say:
cat source.json | jq '
{ InstanceId, Tags } |
(.Tags[] | select( .Key == "environment" ))
'
I get some of what I want, but I cannot figure out how to include InstanceId in the output nor how to incorporate the "and" part of the select.
Here is a simple but efficient approach using any:
select( any(.Tags[]; .Key=="environment" and .Value == "ops") )
| .InstanceId
An alternative approach that avoids .Tags[]:
{"Key": "environment", "Value": "ops"} as $object
| select( .Tags | index($object) )
| .InstanceId
I'm not sure if this is the exact output you're looking for (comment if it isn't), but this will output the InstanceIds of JSON objects that contain a Tag with Key environment and Value ops.
jq 'select( .Tags[] | (.Key == "environment" and .Value == "ops")) | .InstanceId' < source.json

jq: How to replace element in an array or add it if it doesn't exist

Given the following json structure:
{
"elements": [
{
"name": "disregard",
"value": "me"
},
{
"name": "foo",
"value": "bar"
},
{
"name": "dont-edit",
"value": "me"
}
]
}
What would be the appropriate jq query to replace the value of the name: foo element or create/add the element to the array, if it doesn't already exist?
Here is a safe if pedestrian solution:
.elements
|= (map(.name) | index("foo")) as $ix
| if $ix
then .[$ix]["value"] = "BAR"
else . + [{name: "foo", value: "BAR"}]
end
You might want to abstract away the "foo" and "BAR" bits:
upsert
# Input is assumed to be an array of {name:_, value:_} objects
def upsert($foo; $bar):
(map(.name) | index($foo)) as $ix
| if $ix then .[$ix]["value"] = $bar else . + [{name: $foo, value: $bar}] end;
Usage:
.elements |= upsert("foo"; "BAR")

How to substitute strings in JSON with jq based on the input

Given the input in this form
[
{
"DIR" : "/foo/bar/a/b/c",
"OUT" : "/foo/bar/x/y/z",
"ARG" : [ "aaa", "bbb", "/foo/bar/a", "BASE=/foo/bar" ]
},
{
"DIR" : "/foo/baz/d/e/f",
"OUT" : "/foo/baz/x/y/z",
"ARG" : [ "ccc", "ddd", "/foo/baz/b", "BASE=/foo/baz" ]
},
{
"foo" : "bar"
}
]
I'm trying to find out how to make jq transform that into this:
[
{
"DIR" : "BASE/a/b/c",
"OUT" : "BASE/x/y/z",
"ARG" : [ "aaa", "bbb", "BASE/a", "BASE=/foo/bar" ]
},
{
"DIR" : "BASE/d/e/f",
"OUT" : "BASE/x/y/z",
"ARG" : [ "ccc", "ddd", "BASE/b", "BASE=/foo/baz" ]
},
{
"foo" : "bar"
}
]
In other words, objects having an "ARG" array, containing a string that starts with "BASE=" should use the string after "BASE=", e.g. "/foo" to substitute other string values that start with "/foo" (except the "BASE=/foo" which should remain unchanged")
I'm not even close to finding a solution myself, and at this point I'm unsure that jq alone will do the job.
With jq:
#!/usr/bin/jq -f
# fix-base.jq
def fix_base:
(.ARG[] | select(startswith("BASE=")) | split("=")[1]) as $base
| .DIR?|="BASE"+ltrimstr($base)
| .OUT?|="BASE"+ltrimstr($base)
| .ARG|=map(if startswith($base) then "BASE"+ltrimstr($base) else . end)
;
map(if .ARG? then fix_base else . end)
You can run it like this:
jq -f fix-base.jq input.json
or make it an executable like this:
chmod +x fix-base.jq
./fix-base.jq input.json
Don't worry, jq alone will do the job:
jq 'def sub_base($base): if (startswith("BASE") | not) then sub($base; "BASE") else . end;
map(if .["ARG"] then ((.ARG[] | select(startswith("BASE=")) | split("=")[1]) as $base
| to_entries
| map(if (.value | type == "string") then .value |= sub_base($base)
else .value |= map(sub_base($base)) end)
| from_entries)
else . end)' input.json
The output:
[
{
"DIR": "BASE/a/b/c",
"OUT": "BASE/x/y/z",
"ARG": [
"aaa",
"bbb",
"BASE/a",
"BASE=/foo/bar"
]
},
{
"DIR": "BASE/d/e/f",
"OUT": "BASE/x/y/z",
"ARG": [
"ccc",
"ddd",
"BASE/b",
"BASE=/foo/baz"
]
},
{
"foo": "bar"
}
]
Some helper functions make the going much easier. The first is generic and worthy perhaps of your standard library:
# Returns the integer index, $i, corresponding to the first element
# at which f is truthy, else null
def indexof(f):
label $out
| foreach .[] as $x (null; .+1;
if ($x|f) then (.-1, break $out) else empty end) // null;
# Change the string $base to BASE using gsub
def munge($base):
if type == "string" and (test("^BASE=")|not) then gsub($base; "BASE")
elif type=="array" then map(munge($base))
elif type=="object" then map_values(munge($base))
else .
end;
And now the easy part:
map(if has("ARG")
then (.ARG|indexof(test("^BASE="))) as $ix
| if $ix
then (.ARG[$ix]|sub("^BASE=";"")) as $base | munge($base)
else . end
else . end )
Some points to note:
You may wish to use sub rather than gsub in munge;
The above solution assumes you want to make the change in all keys, not just "DIR", "OUT", and "ARG"
The above solution allows specifications of BASE that include one or more occurrences of "=".