How to Handle Missing Nested JSON Data in Ansible URI Responses with Conditional Logic


2 views

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