How to skip null values in json query? - json

Im trying to pull values from a json file but I do not want values that are null? How can I easily skip null values?
I am just trying to find the easiest and simplest solution to parsing JSON into YALM and not have formatting errors in YAML. Maybe I am over complicating it.
JSON:
{
"configuration": {
"protocols" : {
"bgp" : {
"group" : [
{
"name" : "IBGP",
"neighbor" : [
{
"name" : "192.168.1.2",
"description" : "router-2"
}
]
},
{
"name" : "EBGP",
"neighbor" : [
{
"name" : "192.168.2.2",
"description" : "router-3",
"local-address" : "192.168.2.1",
"peer-as" : "65555"
}
]
}
]
}
}
}
}
Play:
- name: parse bgp
set_fact:
bgp: "{{ read | to_json | from_json | json_query(bgp_var) }}"
vars:
bgp_var: 'configuration.protocols.bgp.group[].{group_name:name,neighbors:[{peer_ip:neighbor[].name | [0], peer_description:neighbor[].description | [0], peer_as:neighbor[]."peer-as" | [0] }]}'
- copy:
content: >-
routing_protocols:
bgp:
{{ bgp | to_nice_yaml(indent=2, width=1337, sort_keys=False) | indent(4) }}
dest: "{{ inventory_hostname }}_routing_protocols.yaml"
Returns:
routing_protocols:
bgp:
- group_name: IBGP
neighbors:
- peer_as: null # < Is there a way to skip this anytime a value is null?
peer_description: router-2
peer_ip: 192.168.1.2
- group_name: EBGP
neighbors:
- peer_as: '65555'
peer_description: router-3
peer_ip: 192.168.2.2

you could use this playbook with the parameter omit:
tasks:
- name: set var
set_fact:
bgp: >-
{{ bgp | d([]) +
[ {'group_name': _gpname,
'neighbors': [{'peer_description': _desc, 'peer_ip': _peerip, 'peer_as': _peeras}]
}
] }}
loop: "{{ configuration.protocols.bgp.group }}"
vars:
_gpname: "{{ item.name }}"
_desc: "{{ item.neighbor.0.description }}"
_peeras: "{{ item.neighbor[0]['peer-as'] | d(omit)}}"
_peerip: "{{ item.neighbor.0.name }}"
- name: displ
debug:
msg: "{{ bgp }}"
result:
ok: [localhost] => {
"msg": [
{
"group_name": "IBGP",
"neighbors": [
{
"peer_description": "router-2",
"peer_ip": "192.168.1.2"
}
]
},
{
"group_name": "EBGP",
"neighbors": [
{
"peer_as": "65555",
"peer_description": "router-3",
"peer_ip": "192.168.2.2"
}
]
}
]
}

You can make it in one go in JMESPath, but that would require a double query on the property neighbour, as JMESPath cannot omit a property as Ansible can (for that, see #Frenchy's answer).
So, you would have to have list of list, into which, the first sub list would consist of the neighbour that would contain the key peer-as, and the second sub list, the one that would not contain it. Then you will have to flatten this list of list.
So, here is the copy task:
- copy:
content: >-
{{
read
| to_json
| from_json
| json_query(query)
| to_nice_yaml(indent=2)
}}
dest: "{{ inventory_hostname }}_routing_protocols.yaml"
vars:
query: >-
{
routing_protocols: {
bgp: configuration.protocols.bgp.group[].{
group_name: name,
neighbors: [
neighbor[?"peer-as"].{
peer_description: description,
peer_ip: name,
peer_as: "peer-as"
},
neighbor[?"peer-as" == null].{
peer_description: description,
peer_ip: name
}
]|[]
}
}
}
After this task, you will end with a YAML file containing your expected:
routing_protocols:
bgp:
- group_name: IBGP
neighbors:
- peer_description: router-2
peer_ip: 192.168.1.2
- group_name: EBGP
neighbors:
- peer_as: '65555'
peer_description: router-3
peer_ip: 192.168.2.2

Related

Unable to read json data in ansible play

I have the below json data file:
[
{
"?xml": {
"attributes": {
"encoding": "UTF-8",
"version": "1.0"
}
}
},
{
"domain": [
{
"name": "mydom"
},
{
"domain-version": "12.2.1.3.0"
},
{
"server": [
{
"name": "AdminServer"
},
{
"ssl": {
"name": "AdminServer"
}
},
{
"listen-port": "12400"
},
{
"listen-address": "mydom.myserver1.mybank.com"
}
]
},
{
"server": [
{
"name": "SERV01"
},
{
"log": [
{
"name": "SERV01"
},
{
"file-name": "/web/bea_logs/domains/mydom/SERV01/SERV01.log"
}
]
},
{
"listen-port": "12401"
},
{
"listen-address": "mydom.myserver1.mybank.com"
},
{
"server-start": [
{
"java-vendor": "Sun"
},
{
"java-home": "/web/bea/platform1221/jdk"
}
]
}
]
},
{
"server": [
{
"name": "SERV02"
},
{
"log": [
{
"name": "SERV02"
},
{
"file-name": "/web/bea_logs/domains/mydom/SERV02/SERV02.log"
}
]
},
{
"listen-port": "12401"
},
{
"listen-address": "mydom.myhost2.mybank.com"
},
{
"server-start": [
{
"java-home": "/web/bea/platform1221/jdk"
} ]
}
]
}
]
}
]
I wish to display all the server names and their respective port numbers.
Below is my failed attempt to display all the server names viz
AdminServer
SERV01
SERV02
My playbook:
tasks:
- name: Read the JSON file content in a variable
shell: "cat {{ playbook_dir }}/tmpfiles/{{ Latest_Build_Number }}/testme.json"
register: result
- name: Server Names
set_fact:
servernames: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: '*.domain.server[*].name'
- name: Server Names and Ports
set_fact:
serverinfo: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: '*.server[*].[name, port]'
- name: Print all server names
debug:
msg: "{{ item}}"
with_items:
- "{{ servernames }}"
I also tried the below:
jmesquery: 'domain.server[*].name'
There is no error but no data in the output as well. Output below:
TASK [Print all server names] *********************************************************************************
Monday 21 February 2022 03:07:47 -0600 (0:00:00.129) 0:00:03.590 *******
ok: [localhost] => (item=) => {
"msg": ""
}
Can you please suggest how can I get the desired data?
lot of solutions, one
with jmespath, you could try this:
tasks:
- name: Read the JSON file content in a variable
shell: "cat testme.json"
register: result
- name: jsondata
set_fact:
jsondata: "{{ result.stdout | from_json }}"
- name: Server Names
set_fact:
servernames: "{{ servernames | default([]) + [dict(name=item[0], port=item[1])] }}"
loop: "{{ jsondata | json_query(jmesquery0) | zip(jsondata | json_query(jmesquery1)) | list }}"
vars:
jmesquery0: '[].domain[].server[].name'
jmesquery1: '[].domain[].server[]."listen-port"'
- name: debug result
debug:
msg: "{{ servernames }}"
result:
ok: [localhost] => {
"msg": [
{
"name": "AdminServer",
"port": "12400"
},
{
"name": "SERV01",
"port": "12401"
},
{
"name": "SERV02",
"port": "12401"
}
]
}
Due to the nature of your data being in lists, you'll have to resort to conditionals in order to get rid of the empty objects and single item lists that would otherwise pollute your data:
[].domain[?server].server, to get the objects having a property server
[?name].name | [0] to get the name
[?"listen-port"]."listen-port" | [0] to get the port
So, a valid JMESPath query on your data would be
[].domain[?server]
.server[]
.{
name: [?name].name | [0],
port: [?"listen-port"]."listen-port" | [0]
}
And in Ansible, with that single JMESPath query, given that the file is on the controller:
- debug:
var: >-
lookup(
'file',
playbook_dir ~ '/tmpfiles/' ~ Latest_Build_Number ~ '/testme.json'
)
| from_json
| json_query('
[].domain[?server]
.server[]
.{
name: [?name].name | [0],
port: [?"listen-port"]."listen-port" | [0]
}
')
vars:
Latest_Build_Number: 1
This would yield
TASK [debug] *************************************************************************
ok: [localhost] =>
? |-
lookup(
'file',
playbook_dir ~ '/tmpfiles/' ~ Latest_Build_Number ~ '/testme.json'
) | from_json | json_query('
[].domain[?server]
.server[]
.{
name: [?name].name | [0],
port: [?"listen-port"]."listen-port" | [0]
}
')
: - name: AdminServer
port: '12400'
- name: SERV01
port: '12401'
- name: SERV02
port: '12401'
If the file is on the nodes and not on the controller, then, you can either slurp the files first or resort to cat, as you did before applying the same JMESPath query.

ansible json_query to extract value dependant on other field

Looking to extract href value for each flavor from below json when rel==self and I just can't work it out
{
"flavors": [
{
"id": "1",
"links": [
{
"href": "http://127.0.0.1:8764/v2.1/flavors/1",
"rel": "self"
},
{
"href": "http://127.0.0.1:8764/flavors/1",
"rel": "bookmark"
}
],
"name": "m1.tiny"
},
{
"id": "2",
"links": [
{
"href": "http://127.0.0.1:8764/v2.1/flavors/2",
"rel": "self"
},
{
"href": "http://127.0.0.1:8764/flavors/2",
"rel": "bookmark"
}
],
"name": "m1.small"
},
{
"id": "3",
"links": [
{
"href": "http://127.0.0.1:8764/v2.1/flavors/3",
"rel": "self"
},
{
"href": "http://127.0.0.1:8764/flavors/3",
"rel": "bookmark"
}
],
"name": "m1.medium"
}
]
}
I have tried below but just cant get the jmesquery: 'flavors.links[?contains(rel, self)]' query to work?
- name:
uri:
url: 'http://192.168.1.182:8774/v2.1/flavors'
method: GET
body_format: json
headers:
X-Auth-Token: "{{ ansible_facts.auth_token }}"
Content-Type: "application/json; charset=UTF-8"
return_content: yes
register: this
- debug:
msg: "{{ this }}"
- name: save the Json data to a Variable as a Fact
set_fact:
jsondata: "{{ this }}"
- debug:
msg: "{{ jsondata }}"
- name: save the Json data to a Variable as a Fact
set_fact:
flavors: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: 'json.flavors'
- debug:
msg: "{{ flavors }}"
- name: LINKS
set_fact:
links: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: 'flavors.links[?contains(rel, `self`)]'
- debug:
msg: "{{ links }}"
I can print the values for all href using below...
- name: save the Json data to a Variable as a Fact
set_fact:
href: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: 'json.flavors[*].links[].href'
- debug:
msg: "{{ href }}"
Any help appreciated?
Actually, I have eventually worked it out...
- name: save the Json data to a Variable as a Fact
set_fact:
href: "{{ jsondata | json_query(jmesquery) }}"
vars:
jmesquery: json.flavors[].links[?rel==`self`].href
- debug:
msg: "{{ href }}"

Ansible - nested loop over object without knowing object/key name

I'm working on an ansible playbook, I need to get a value from a JSON API result.
I want to get the value of the key "1h" without knowing the previous object (sd#eureka...), and i need to loops over all my results, and all my buckets.
{
"etat_ms_rep": {
"results": [
{
"buckets": {
"sd#eureka#pixidanalyticsbackend#c3fb422bb36882d9f502092fd75fcb34": {
"1h": -1,
"1m": -1
},
"sd#eureka#pixidanalyticsbackend#348fdab155904e22ca0c744d0c052cf8": {
"1h": 100,
"1m": 100
}
}
},
{
"buckets": {
"sd#eureka#pixidorchestratorbackend#8fa3441c6c5caa2d5f0e3264a00be91b": {
"1h": 100,
"1m": 100
},
"sd#eureka#pixidorchestratorbackend#6dc48be83cb86ae1a73b344e9421ed8e": {
"1h": 100,
"1m": 100
}
}
}
]
}
}
I tried that but without success, it doesnt show the value of "1h" key...
- name: Display all bucket info
set_fact:
test: "{{ etat_ms_rep.results | json_query(jmesquery) }}"
vars:
jmesquery: " [*].['1h'] "
For example
- name: Get the value of the key 1h
debug:
msg: "{{ etat_ms_rep.results | json_query(jmesquery) }}"
vars:
jmesquery: '[].*.*.["1h"]'
gives
msg:
- - - - -1
- - 100
- - - - 100
- - 100
Flatten the list if you want to
- name: Get the value of the key 1h
debug:
msg: "{{ etat_ms_rep.results | json_query(jmesquery) | flatten }}"
vars:
jmesquery: '[].*.*.["1h"]'
gives
msg:
- -1
- 100
- 100
- 100

Ansible Dictionary value not set

I'm trying to fill a value in a dictionary with ansible but apparently is not doing it.
I do like this:
- name: Fill with zeros
set_fact:
item: "{{ item | combine(zero_fill, recursive=true) }}"
vars:
zero_fill: { 'json' : { 'data': { 'result': [{ 'value' : ["0.0","0.0"]}]}}}
when: item.json.data.result == []
with_items:
- "{{ requests.results }}"
One item from this variable is like this:
{
...
"json": {
"data": {
"result": [],
"resultType": "vector"
}
}
...
}
The point is that in the output of this task I do see the value added, but when I print it just right after the task, the value is not there.
For example, create a new list results_zf and update the dictionary json.data with zero_fill when json.data.result is an empty list
- set_fact:
results_zf: "{{ results_zf|default([]) + [_item] }}"
loop: "{{ requests.results }}"
vars:
_item: "{{ (item.json.data.result|length > 0)|
ternary(item,
{'json': {'data': item.json.data|combine(zero_fill)}})
}}"
Limit the dictionary zero_fill to the attribute result
zero_fill:
'result': [{'value': ["0.0","0.0"]}]

Using item in Ansible json_query

I'm trying to loop through a list of keys to grab associated names from some json:
- name: show names
debug:
msg: "{{ data.json | json_query(query) }}"
vars:
query: "[? key==item].name"
with_items: "{{ keys.split() }}"
But it never displays properly when I try to run it. The keys are correct, but no data is returned:
TASK [get_help_on_SO: show]
ok: [localhost] => (item=Key1) => {
"msg": []
}
ok: [localhost] => (item=Key2) => {
"msg": []
}
Manually putting in the code works just fine so my query syntax seems to be right:
query: "[? key==`Key1`].name"
TASK [get_help_on_SO : show]
ok: [localhost] => (item=Key1) => {
"msg": [
"FooBar 1"
]
}
ok: [localhost] => (item=Key2) => {
"msg": [
"FooBar 1"
]
}
How can I properly pass the item into the json_query?
You didn't surround the item variable with any Jinja delimiters, so it is not interpreted.
You end testing if the key is equal to the string 'item' and not to the string stored in the variable item.
- name: show names
debug:
msg: "{{ data.json | json_query(query) }}"
vars:
query: "[?key==`{{ item }}`].name"
with_items: "{{ keys.split() }}"
Given the data
keys: 'key1 key3'
data:
json: [{
"key": "key1",
"name": "name1"
},
{
"key": "key2",
"name": "name2"
},
{
"key": "key3",
"name": "name3"
}
]
the expected result is
- name1
- name3
It's possible to avoid both the loop and json_query and simplify the solution. The task below
- name: show names
debug:
msg: "{{ data.json|
selectattr('key', 'in', my_keys)|
map(attribute='name')|
list }}"
vars:
my_keys: "{{ keys.split() }}"
gives
msg:
- name1
- name3