Use the combine filter.
- set_fact:
mergedobject: "{{ object1.params | combine (object2.params) }}"
Answer from Thomas Hirsch on Stack Overflowjmespath - Combine attribute value using json_query in ansible - Stack Overflow
How do I create and store a list of dict/json objects?
Ansible combine 'setup' output with task output to JSON - Stack Overflow
See how merge json with another in ansible
For example
- debug:
msg: "{{ locations|
json_query('[?state == `WA`].[name,state]')|
map('join', '-')|list }}"
gives
msg:
- Seattle-WA
- Bellevue-WA
- Olympia-WA
The same result gives the task below using Jinja2 filters only
- debug:
msg: "{{ _names|zip(_states)|map('join', '-')|list }}"
vars:
_locations: "{{ locations|selectattr('state', 'eq', 'WA')|list }}"
_names: "{{ _locations|map(attribute='name')|list }}"
_states: "{{ _locations|map(attribute='state')|list }}"
json_query issue (fixed in 2.10 and later)
There is JMESPath join. Unfortunately
- debug:
msg: "{{ locations|
json_query('[].join(`-`, [name,state])') }}"
fails
msg: |- JMESPathError in json_query filter plugin: In function join(), invalid type for value: Seattle, expected one of: ['array-string'], received: "AnsibleUnicode"
to_json|from_json workaround
Quoting from json_query: Add examples for starts_with and contains #72821
data structure returned from register variables needs to be parsed using to_json | from_json in order to get a correct result. Fixes: ansible-collections/community.general#320
- debug:
msg: "{{ locations|to_json|from_json|
json_query('[].join(`-`, [name,state])') }}"
gives
msg:
- Seattle-WA
- New York-NY
- Bellevue-WA
- Olympia-WA
Just for the sake of a pure JMESPath way of doing it, as your trial and error solution still have an unneeded extra layer of complexity.
When you are doing
[?state == 'WA'].[join('-', [name, state])][]
You are creating an array [join('-', [name, state])] then flattening it [] for no reason.
You can just go to the solution with a shorter approach:
[?state == `WA`].join(`-`, [name, state])
Also mind that you can overcome the quotes in quotes (simple or double) complication for JMESPath queries using:
YAML multilines string: How do I break a string in YAML over multiple lines?
Backticks in your JMESPath query, as pointed in the documentation:
In the example above, quoting literals using backticks avoids escaping quotes and maintains readability.
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#selecting-json-data-json-queries
So you end up with (see note below if you are on an Ansible version < 2.10):
- debug:
msg: >-
{{ test.locations
| json_query('[?state == `WA`].join(`-`, [name, state])') }}
Please note: as raised by @Vladimir Botka on the versions prior to 2.10, you will be affected by this issue: https://github.com/ansible/ansible/issues/27299#issuecomment-331068246, forcing you to add a | to_json | from_json filter on the list.
Given the playbook:
- hosts: all
gather_facts: yes
tasks:
- debug:
msg: >-
{{ test.locations
| json_query('[?state == `WA`].join(`-`, [name, state])')
}}
vars:
test:
locations:
- name: Seattle
state: WA
- name: New York
state: NY
- name: Bellevue
state: WA
- name: Olympia
state: WA
This yields:
[
"Seattle-WA",
"Bellevue-WA",
"Olympia-WA"
]
Using the input from @KonstantinSuvorov I came up with this:
---
- hosts: kvm_hosts
gather_facts: no
tasks:
- setup:
register: setup_res
- virt: "command=list_vms"
register: cmd_res
- copy:
content: "{{ setup_res | combine(cmd_res) | to_nice_json }}"
dest: /tmp/out/{{ inventory_hostname }}.json
delegate_to: localhost
You can do something like this:
---
- hosts: all
gather_facts: no
tasks:
- setup:
register: setup_res
- command: echo ok
register: cmd_res
- file:
path: /tmp/out/{{ inventory_hostname }}
state: directory
delegate_to: localhost
- copy:
content: "{{ setup_res | to_nice_json }}"
dest: /tmp/out/{{ inventory_hostname }}/facts.json
delegate_to: localhost
- copy:
content: "{{ cmd_res | to_nice_json }}"
dest: /tmp/out/{{ inventory_hostname }}/cmd.json
delegate_to: localhost
replace command call with virt.
Is this the code that you're looking for?
Copy - set_fact:
doc1: "{{ lookup('file', 'config.json') }}"
doc2: "{{ lookup('file', 'credHelper.json') }}"
- debug: msg="{{ doc1 | combine(doc2) }}"
TASK [debug] **********************************************************
ok: [localhost] => {
"msg": {
"HttpHeaders": {
"User-Agent": "Docker-Client/18.03.0-ce (windows)"
},
"auths": {
"https://index.docker.io/v1/": {
"auth": "supercalifragilistic"
}
},
"credHelpers": {
"111111111111.dkr.ecr.us-east-1.amazonaws.com": "ecr-login"
}
}
}
Just to complete the picture, this is also possible with jq, simply add two json files:
Copyjq -s 'add' credHelpers.json config.json
The add filter takes as input an array, and produces as output the elements of the array added together, as the jq documentation says. In addition you need the --slurp/-s command line option, which turns the entire input stream into a large array and runs the filter just once.
As @DustWolf notes in the comments,
For anyone from the Internet looking for the answer to: "How tp combine nested dictionaries in ansible", the answer is
| combine(new_item, recursive=true)
This solves a closely related issue that has baffled myself and my team for months.
I will demonstrate:
Code:
---
- hosts: localhost
gather_facts: false
vars:
my_default_values:
key1: value1
key2:
a: 10
b: 20
my_custom_values:
key3: value3
key2:
a: 30
my_values: "{{ my_default_values | combine(my_custom_values, recursive=true) }}"
tasks:
- debug: var=my_default_values
- debug: var=my_values
Output:
ok: [localhost] =>
my_values:
key1: value1
key2:
a: 30
key3: value3
Note how key2 was completely replaced, thus losing key2.b
We changed this to:
my_values: "{{ my_default_values | combine(my_custom_values, recursive=true) }}"
Output:
my_values:
key1: value1
key2:
a: 30
b: 20
key3: value3
This syntax is not legal, or at the very least doesn't do what you think:
new_item: "{ '{{item.key}}' : { 'landscape': '{{landscape_dictionary[item.key]|default(false)}}' } }"
Foremost, ansible will only auto-coerce JSON strings into a dict, but you have used python syntax.
Secondarily, the way to dynamically construct a dict is not to use jinja2 to build up text but rather use the fact that jinja2 is almost a programming language:
new_item: "{{
{
item.key: {
'landscape': landscape_dictionary[item.key]|default(false)
}
}
}}"
Any time you find yourself with nested jinja2 interpolation blocks, that is a code smell that you are thinking about the problem too much as text (by nested, I mean {{ something {{nested}} else }})