How do you exclude a property? - json

I have the following dataset:
[
{
"py/object": "bit.ast.Node",
"_children": [
{
"py/object": "bit.ast.Node",
"_children": [
"main",
{
"py/object": "bit.ast.Node",
"_children": [
"args",
{
"py/object": "bit.ast.Node",
"_children": [
{
"py/object": "bit.ast.Node",
"_children": [
"str"
],
"source_column": 2,
"source_filename": "tests/fixture/hello.b",
"source_line": 1,
"tag": "type-named"
}
],
"base": {
"py/id": 10
},
"source_column": 2,
"source_filename": "tests/fixture/hello.b",
"source_line": 1,
"tag": "type",
"type": "array"
}
],
(and so on...)
How do I get jq to exclude the _children property from all objects that have it? What about all properties that start with _?
None of the following seem to work:
jq 'map(del (._children))'
jq 'map(if has("_children") then del (._children) end)'
jq 'del(._children)'
jq 'del(.[]._children)'
jq 'del(.[]|._children)'
I keep getting an error similar to:
jq: error (at <stdin>:1): Cannot index string with string "_children"

exclude the _children property from all objects that have it
If your jq has walk/1 then you could:
walk( if type == "object" then del(._children) else . end )
If not, first include its jq definition (readily googleable) e.g. in ~/.jq
What about all properties that start with _ ?
For this, you could also use walk/1. For clarity and maintainability, it would make sense to define a helper function:
def deleteall(f): with_entries(select(.key | f | not ));
Which you'd invoke as: deleteall( startswith("_") )

Related

Modifying array of key value in JSON jq

In case, I have an original json look like the following:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "user"
}
]
}
]
}
}
And I would like to inplace modify the value for the matched key like so:
jq '.taskDefinition.containerDefinitions[0].environment[] | select(.name=="DB_USERNAME") | .value="new"' json
I got the output
{
"name": "DB_USERNAME",
"value": "new"
}
But I want more like in-place modify or the whole json from the original with new value modified, like this:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "new"
}
]
}
]
}
}
Is it possible to do with jq or any known workaround?
Thank you.
Updated
For anyone looking for editing multi-values,
here is the approach I use
JQ=""
for e in DB_HOST=rds DB_USERNAME=xxx; do
k=${e%=*}
v=${e##*=}
JQ+="(.taskDefinition.containerDefinitions[0].environment[] | select(.name==\"$k\") | .value) |= \"$v\" | "
done
jq '${JQ%??}' json
I think there should be more concise way, but this seems working fine.
It is enough to assign to the path, if you are using |=, e.g.
jq '
(.taskDefinition.containerDefinitions[0].environment[] |
select(.name=="DB_USERNAME") | .value) |= "new"
' infile.json
Output:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "new"
}
]
}
]
}
}
Here is a select-free solution using |=:
.taskDefinition.containerDefinitions[0].environment |=
map(if .name=="DB_USERNAME" then .value = "new"
else . end)
Avoiding select within the expression on the LHS of |= makes the solution more robust w.r.t. the version of jq being used.
You might like to consider this alternative to using |=:
walk( if type=="object" and .name=="DB_USERNAME"
then .value="new" else . end)

jq recursively update values for certain elements

The intent for the JSON data below is to update the value of the field dst with the value of src within all elements of type t, regardless of depth within the tree, while at the same time preserving the whole structure of the data.
Is this possible with jq? My several attempts have boiled down to the following command that is not working to achieve the intended purpose:
$ jq -r 'map_values(select(.. | .type? == "t" |= (.dst = .src)))'
{
"a": "b",
"c": [
{
"type": "t",
"src": "xx",
"dst": "zz"
},
{
"type": "t",
"src": "xx",
"dst": "zz"
}
],
"d": [
{
"e": [
{
"type": "t",
"src": "xx",
"dst": "zz"
}
]
},
{
"type": "t2",
"src": "xx",
"dst": "zz"
}
]
}
Is this possible with jq?
jq is Turing-complete :-)
Here's a simple solution:
walk( if type == "object" and .type == "t" then .dst = .src else . end)
If your jq does not have walk/1, then it might be a good time to upgrade (to jq 1.6); otherwise, you can snarf its def from the web, e.g. by googling: jq "def walk"
Alternatively ...
reduce paths as $x (.;
if (getpath($x)|.type? // false) == "t"
then setpath( $x + ["dst"]; getpath( $x + ["src"] ))
else . end)

Non destructive assignation with jq

I got the following data:
{
"things": [
{
"name": "lkj",
"something": [
"hike"
],
"more_data": "important",
"other_stuff": "very important"
},
{
"name": "iou",
"different_more_data": "very important too",
"more_different_data": [
"even more"
]
}
]
}
Each of things has an id called "name", with jq I can edit it like:
jq '(.things[]) |= {name,something:["changed"]}'
{
"things": [
{
"name": "lkj",
"something": [
"changed"
]
},
...
Unfortunately I lose everything not declared in the right hand of the assignation operation.
Is there a way to make assignations without losing data? So that the result is like this:
{
"things": [
{
"name": "lkj",
"something": [
"changed"
],
"more_data": "important",
"other_stuff": "very important"
},
{
"name": "iou",
"something": [
"changed"
],
"different_more_data": "very important too",
"more_different_data": [
"even more"
]
}
]
}
You can simply modify your query so that it looks like:
.things[] |= (.something = ["changed"])
You can also use |= (or one of its siblings, such as +=) instead of = in the RHS expression, e.g.
.things[] |= (.something += ["changed"])
If you want to update some, but not all, items, you can still use the above forms. A straightforward approach is to use if ... then ... else ... end, for example:
.things[] |= (if .name == "lkj" then .something = ["changed"] else . end)
Using select on the LHS of |=
jq (or at least jq since version 1.4) does support the use of select on the LHS of |=, e.g.
(.things[] | select(.name=="lkj")) |= (.something += ["changed"])
With jq's map function:
jq '.things |= map(.something = ["changed"])' jsonfile
map(x) - apply specified filter x for each item of the input array
.something = ["changed"] - set key something to an object with array ["changed"] as a value
The output:
{
"things": [
{
"name": "lkj",
"something": [
"changed"
],
"more_data": "important",
"other_stuff": "very important"
},
{
"name": "iou",
"different_more_data": "very important too",
"more_different_data": [
"even more"
],
"something": [
"changed"
]
}
]
}

How do I update a single value in a nested array of objects in a json document using jq?

I have a JSON document that looks like the following. Note this is a simplified example of the real JSON, which is included at bottom of question:
{
"some_array": [
{
"k1": "A",
"k2": "XXX"
},
{
"k1": "B",
"k2": "YYY"
}
]
}
I would like to change the value of all the k2 keys in the some_array array where the value of the k1 key is "B".
Is this possible using jq ?
For reference this is the actual JSON document, which is an environment variable file for use in postman / newman tool. I am attempting this conversion using JQ because the tool does not yet support command line overrides of specific environment variables
Actual JSON
{
"name": "Local-Stack-Env-Config",
"values": [
{
"enabled": true,
"key": "KC_master_host",
"type": "text",
"value": "http://localhost:8087"
},
{
"enabled": true,
"key": "KC_user_guid",
"type": "text",
"value": "11111111-1111-1111-1111-11111111111"
}
],
"timestamp": 1502768145037,
"_postman_variable_scope": "environment",
"_postman_exported_at": "2017-08-15T03:36:41.474Z",
"_postman_exported_using": "Postman/5.1.3"
}
Here is a slightly simpler version of zayquan's filter:
.some_array |= map(if .k1=="B" then .k2="changed" else . end)
Here's another solution.
jq '(.some_array[] | select(.k1 == "B") | .k2) |= "new_value"'
Output
{
"some_array": [
{
"k1": "A",
"k2": "XXX"
},
{
"k1": "B",
"k2": "new_value"
}
]
}
Here is a viable solution:
cat some.json | jq '.some_array = (.some_array | map(if .k1 == "B" then . + {"k2":"changed"} else . end))'
produces the output:
"some_array": [
{
"k1": "A",
"k2": "XXX"
},
{
"k1": "B",
"k2": "changed"
}
]
}

Parsing object to remove entries

How would I parse this object, removing all entries where "field"="status", but at the same time retaining the validity of the JSON object (i.e. also removing the extraneous closing braces or brackets at the end of the object?
{"logic":"and","filters":[{"filters":[{"field":"name","operator":"contains","value":"JOHNSON"},{"field":"city","operator":"contains","value":"MILWAUKEE"}],"logic":"and"},{"logic":"or","filters":[{"field":"status","operator":"eq","value":"A"},{"field":"status","operator":"eq","value":"G"},{"field":"status","operator":"eq","value":"O"},{"field":"status","operator":"eq","value":"P"},{"field":"status","operator":"eq","value":"S"}]}]
Here is a solution using jq based on my answer to a similar question
def clean(condition):
if type == "object" then
if condition
then empty
else
with_entries(
if (.value|type) == "object" and (.value|condition)
then empty
else .value |= clean(condition)
end
)
end
elif type == "array" then
map(
if type == "object" and condition
then empty
else clean(condition)
end
)
else .
end
;
clean(
has("field") and (.field == "status")
)
If filter.jq contains this filter and data.json contains the sample data then the command
$ jq -M -f filter.jq data.json
produces
{
"logic": "and",
"filters": [
{
"filters": [
{
"field": "name",
"operator": "contains",
"value": "JOHNSON"
},
{
"field": "city",
"operator": "contains",
"value": "MILWAUKEE"
}
],
"logic": "and"
},
{
"logic": "or",
"filters": []
}
]
}