When working with API responses in Ansible, a common pain point emerges when dealing with optional nested JSON fields. The default behavior fails tasks when trying to access undefined variables in Jinja templates, even with ignore_errors: yes
set. This creates unreliable playbooks when dealing with dynamic API responses.
Here are three approaches to handle this scenario effectively:
# Method 1: Using the 'default' filter
- name: Write JSON value or default
copy:
content: "{{ json_response.json.nested1.nested2 | default('DEFAULT_VALUE') }}"
dest: "/tmp/foo.txt"
# Method 2: Explicit conditional check
- name: Check and write JSON value
copy:
content: "{% if json_response.json.nested1.nested2 is defined %}{{ json_response.json.nested1.nested2 }}{% else %}DEFAULT_VALUE{% endif %}"
dest: "/tmp/foo.txt"
# Method 3: Combined approach with error handling
- name: Get JSON from API
uri:
url: "http://whatever.com/jsonresponse"
return_content: yes
status_code: 200
register: json_response
ignore_errors: yes
changed_when: false
- name: Process response
block:
- name: Extract nested value
set_fact:
output_value: "{{ json_response.json.nested1.nested2 }}"
when: json_response.json.nested1.nested2 is defined
- name: Set default if missing
set_fact:
output_value: "DEFAULT_VALUE"
when: json_response.json.nested1.nested2 is not defined
- name: Write final value
copy:
content: "{{ output_value }}"
dest: "/tmp/foo.txt"
For deeply nested structures where any level might be missing:
- name: Safe nested access with recursive default
set_fact:
final_value: >-
{{
json_response.json |
default({}) |
json_query('nested1.nested2') |
default('DEFAULT_VALUE')
}}
- Always validate the HTTP response code before processing JSON
- Use
failed_when
to properly handle API errors - Consider implementing custom filters for complex JSON parsing
- Log both successful and failed attempts for debugging
---
- hosts: localhost
gather_facts: false
tasks:
- name: Fetch API data
uri:
url: "https://api.example.com/data"
method: GET
return_content: yes
status_code: 200
register: api_response
ignore_errors: yes
- name: Process nested data safely
block:
- name: Set default structure if request failed
set_fact:
api_data: {}
when: api_response is failed or api_response.json is not defined
- name: Extract value with fallbacks
set_fact:
result_value: >-
{{
(api_response.json.nested.level1.level2 | default('N/A')) if
(api_response.json.nested.level1 is defined) else 'DEFAULT'
}}
- name: Output result
debug:
msg: "Final value: {{ result_value }}"
When working with REST APIs in Ansible playbooks, we often need to handle JSON responses where certain nested fields might be missing. The common approach using register
and direct variable access can cause playbook failures when dealing with undefined nested structures.
- name: Fetch API data
uri:
url: "https://api.example.com/data"
return_content: yes
register: api_response
ignore_errors: yes # This only handles HTTP errors, not missing JSON keys
Here are several effective methods to safely access nested JSON data with fallback values:
Method 1: Using the default filter
- name: Write nested JSON with fallback
copy:
content: "{{ (api_response.json.nested1.nested2 | default('default_value')) }}"
dest: "/tmp/foo.txt"
Method 2: Combining with json_query
- name: Extract nested value safely
set_fact:
extracted_value: "{{ api_response.json | json_query('nested1.nested2') | default('fallback_value') }}"
- name: Write to file
copy:
content: "{{ extracted_value }}"
dest: "/tmp/foo.txt"
Method 3: Complete Error Handling Approach
- name: Check if nested structure exists
set_fact:
has_nested_data: "{{ api_response.json.nested1 is defined and api_response.json.nested1.nested2 is defined }}"
- name: Process response
block:
- name: Write nested data
copy:
content: "{{ api_response.json.nested1.nested2 }}"
dest: "/tmp/foo.txt"
rescue:
- name: Write default value
copy:
content: "default_content"
dest: "/tmp/foo.txt"
For comprehensive error handling including HTTP failures and JSON parsing:
- name: Attempt API request
block:
- name: Make API call
uri:
url: "https://api.example.com/data"
return_content: yes
register: api_response
- name: Validate JSON structure
assert:
that:
- api_response.json is defined
- api_response.json.nested1 is defined
- api_response.json.nested1.nested2 is defined
fail_msg: "Required JSON structure not found"
- name: Write valid data
copy:
content: "{{ api_response.json.nested1.nested2 }}"
dest: "/tmp/foo.txt"
rescue:
- name: Handle failures
copy:
content: "fallback_content"
dest: "/tmp/foo.txt"
When dealing with large JSON responses, consider these optimizations:
- Use
json_query
for complex nested structures - Cache API responses when possible with
cacheable: yes
- Validate JSON schema early to fail fast