Filter JSON object based on children keys - jsonpath

I am trying to find a procedural way of applying the following filter.
Given the following object, how can I get all top-level keys that contain child c2?
{
"a1" : {
"b" : {
"c1": {},
"c2": {}
}
},
"a2" : {
"b" : {
"c1" : {}
}
},
"a3" : {
"b" : {
"c1" : {},
"c2" : {}
}
}
}
Expected result:
["a1", "a3"]
I need that filter in JSONPath, JMESPath or ObjectPath syntax.
I tried countless combinations of $.*[#.b.c2] in ObjectPath, things like $..[?(b.c2)] in JSONPath, down to constructs like #.* | [?contains(keys(#), 'c2')] or #.*[b.c2] | # in JMESPath - all to no avail.

In ObjectPath selectors work only on lists so $.*[#.b.c2] has nothing to work on. Nested objects like in your example are rare so I didn't implement a way to do what you need. You could try to transform your object to an array like following:
[
{
"name": "a1",
"b": {
"c1": {},
"c2": {}
}
},
{
"name": "a2",
"b": {
"c1": {}
}
},
{
"name": "a3",
"b": {
"c1": {},
"c2": {}
}
}
]
and then use selectors.
$.*[#.b.c2 is not null].name
PS. Use the newest ObjectPath version from Github. I fixed some issues with null comparison just a minute ago.

Related

Gremlin Query (JSON Output) using tree

Query:
g.withSack(0).V().hasLabel('A').has('label_A','A').union(__.emit().repeat(sack(sum).by(constant(1)).in()),emit().repeat(sack(sum).by(constant(-1)).out())).project('level','properties').by(sack()).by(tree().by(valueMap().by(unfold())).unfold())
Output:
{
"level": 1,
"properties": {
"key": {
"label_A": "A"
},
"value": {
"{label_A=A}": {}
}
}
},
{
"level": 2,
"properties": {
"key": {
"label_A": "A"
},
"value": {
"{label_A=A}": {}
}
}
}
}
Getting keys in json format but not values. Please suggest changes in query to acheive the values in json format.
The tree() step returns a tree structure in the form of a Map of Map instances essentially so the output is about what you can expect. In this case, I wonder if you need to use tree() and could instead get by with path() as it seems like it accomplishes the same result without the added structure:
g.withSack(0).
V().hasLabel('A').has('label_A','A').
union(__.emit().repeat(sack(sum).by(constant(1)).in()),
emit().repeat(sack(sum).by(constant(-1)).out())).
project('level','properties').
by(sack()).
by(path().by(elementMap()))

How do I use a value as a key reference in jq?

I have two JSON files as follows.
One contains a mapping of project to owners.
owners.json
{
"Project1": "owner1",
"Project2": "owner2"
}
The second contains a list of projects with extra information:
projects.json
[
{
"name": "Project1",
"base": "A"
},
{
"name": "Project2",
"base": "B"
}
]
I'd like to use JQ to merge the two files to look like the following:
output.json
[
{
"name": "Project1",
"owner": "owner1",
"base": "A"
},
{
"name": "Project2",
"owner": "owner2",
"base": "B"
}
]
My first thought was to try something like this (assuming projects.json is fed on stdin):
jq --slurpFile owners owners.json '.name as $n | [.[] | {name, base, owner: $owners[0].$n}]'
This gives a syntax error relating to the $n in $owners[0].$n. What's the right way to do this in JQ?
Thanks!
You need to wrap variable references in square brackets for indexing objects with them. Even though you corrected that your script wouldn't work as arrays can't be indexed with strings (.name as $n part).
And don't bother with slurpfile, there are simpler ways.
$ jq 'input as $owners | map(.owner = $owners[.name])' projects.json owners.json
[
{
"name": "Project1",
"base": "A",
"owner": "owner1"
},
{
"name": "Project2",
"base": "B",
"owner": "owner2"
}
]

Updating item in DynamoDB fails for the UpdateExpression syntax

My table data looks like below one
{
"id": {
"S": "alpha-rocket"
},
"images": {
"SS": [
"apple/value:50",
"Mango/aa:284_454_51.0.0",
"Mango/bb:291",
"Mango/cc:4"
]
},
"product": {
"S": "fruit"
}
}
Below is my code to update table. The variables I am passing to function has values product_id has alpha-rocket, image_val has 284_454_53.0.0 and image has Mango/aa:284_454_53.0.0.
I am trying to update value of Mango/aa from 284_454_51.0.0 to 284_454_53.0.0 but getting an error "The document path provided in the update expression is invalid for update"
def update_player_score(product_id, image_val, image):
dynamo = boto3.resource('dynamodb')
tbl = dynamo.Table('<TableName>')
result = tbl.update_item(
expression_attribute_names: {
"#image_name" => "image_name"
},
expression_attribute_values: {
":image_val" => image_val,
},
key: {
"product" => "fruit",
"id" => product_id,
},
return_values: "ALL_NEW",
table_name: "orcus",
update_expression: "SET images.#image_val = :image_val",
}
Is there a way to update the value of Mango/aa or replace full string "Mango/aa:284_454_51.0.0" to "Mango/aa:284_454_53.0.0"
You cannot update a string in a list by matching the string. If you know the index of it you can replace the value of the string by index:
SET images[1] = : image_val
It seems like maybe what you want is not a list of strings, but another map. So instead of your data looking like it does you'd make it look like this, which would allow you to do the update you're looking for:
{
"id": {
"S": "alpha-rocket"
},
"images": {
"M": {
"apple" : {
"M": {
"value": {
"S": "50"
}
},
"Mango" : {
"M": {
"aa": {
"S": "284_454_51.0.0"
},
"bb": {
"S": "291"
},
"cc": {
"S": "4"
}
}
}
},
"product": {
"S": "fruit"
}
}
I would also consider putting the different values in different "rows" in the table and using queries to build the objects.

jq - Return all top-level attribute keys which have a subattribute that matches a condition

My JSON looks like:
{
"foo": {
"restricted": true
},
"bar": {
"restricted": false
},
"baz": { }
}
As single line:
{"foo":{"restricted":true},"bar":{"restricted":false},"baz":{}}
I want to get all top-level attribute keys returned which are either restricted == false or don't have the restricted subattribute. With above example JSON, the expected output is:
"bar"
"baz"
I came up with the following expression:
jq "to_entries[] | select(.value.restricted|not) | .key"
to_entries converts the top-level object to an array of objects with a "key" and a "value" attribute each:
[
{
"key": "foo135",
"value": {
"restricted": true
}
},
{
"key": "foo246",
"value": {
"restricted": false
}
},
{
"key": "foo345",
"value": {}
}
]
[] explodes this array. This is done in order to filter individual objects in the next step:
{
"key": "foo135",
"value": {
"restricted": true
}
}
{
"key": "foo246",
"value": {
"restricted": false
}
}
{
"key": "foo345",
"value": {}
}
Then a filter is applied for each object: select(.value.restricted|not). To filter out attributes with the subattribute restricted being truthy, .value.restricted can be inverted with |not to select objects with restricted == false as well as objects without this attribute using a single condition:
{
"key": "foo246",
"value": {
"restricted": false
}
}
{
"key": "foo345",
"value": {}
}
Finally, the "key" attribute is selected: .key. This returns the names of the matching objects (originally the top-level attribute keys):
"foo246"
"foo345"
Note that the pipe character | in select(...) | .key isn't strictly needed. The result is the same without.
If a string array is desired as result...
[
"foo246",
"foo345"
]
... then the following expression can be used:
jq "to_entries | map(select(.value.restricted|not).key)"
As you can see, select() can be used inside of map() to filter out individual elements, and it can even be combined with the attribute filter .key.
If you append [] at the end like map(...)[], then the array gets exploded and the result is a list of strings again, like in the initial example, but using a slightly different approach.

Using jsonpath to get parent node

Using node JSONPath, how can I get the parent node name from child node value
{
"store": {
"book": [
{
"id":"1",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"id":"2",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
}
]
}
}
I use this expression to identify the child node based on value, I want to use this child node to find the parent node
$.[?(#.id =="1")]
You do not specify which implementation of JSON Path, but with both Gatling (Scala) and JayWay (Java) you can use nested filters to filter by children while returning the parent, grandparent or whatever. Here is a sample:
With this JSON:
{
"a": {
"b": {
"c": {
"d": {
"e": "foo"
}
},
"something": "bar"
}
}
}
And this path:
$.a.b[?(#.c[?(#.d[?(#.e == "foo")])])].something
Returns:
[ "bar" ]
I am able to retrieve b by using expressions to get to lower nodes as the filter.
Some other implementations error out on these expressions.
Unfortunately, JSONpath does not support search by parameters in the contents of a child object:
http://goessner.net/articles/JsonPath/
With JayWay it works but the query provided by Jayson is wrong, because if you change the value to "foo2" it will still return "bar".
This can be tested on http://jsonpath.herokuapp.com/.
Query:
$.a.b[?(#.c.d.e=="foo")].something
Returns:
["bar"]
Query:
$.a.b[?(#.c.d.e=="foo2")].something
Returns:
[]

Resources