JSON Usage in Ansible

Last updated: June 29, 2025

My JSON Journey with Ansible

When I first started working with Ansible, I was primarily focused on simple tasks like package installation and service management. But as my automation needs grew more complex, I found myself constantly handling data from APIs, external systems, and configuration files—most of it in JSON format. Learning how to effectively work with JSON in Ansible has been a game-changer for my automation workflows, allowing me to handle complex data transformations that would otherwise require custom scripts. In this post, I'll share my experiences and practical approaches for working with JSON in Ansible, with examples for both Linux and Windows environments.

Understanding JSON in Ansible

JSON (JavaScript Object Notation) has become the lingua franca of data exchange in modern IT systems. Its lightweight, human-readable structure makes it perfect for storing configuration data, API responses, and structured information. In Ansible, JSON is a fundamental data format that powers many internal operations.

JSON Data Types

Before diving into the practical applications, let's quickly review the basic JSON data types you'll encounter in Ansible:

  1. Strings: Text enclosed in quotes ("example")

  2. Numbers: Integer or floating-point values (42, 3.14)

  3. Booleans: True/false values (true, false)

  4. Null: Represents the absence of a value (null)

  5. Arrays: Ordered collections of values (["item1", "item2"])

  6. Objects: Collections of key-value pairs ({"key": "value"})

The most powerful and commonly used JSON structures in Ansible are objects and arrays. JSON objects in Ansible are similar to Python dictionaries, while JSON arrays are similar to Python lists.

JSON Objects in Ansible

In my experience, JSON objects (or dictionaries in Python terms) are the most common JSON structure you'll work with in Ansible. Here's a typical example of a JSON object representation in Ansible:

{
  "server": "web01",
  "location": "us-east",
  "environment": "production",
  "services": {
    "http": {
      "port": 80,
      "enabled": true
    },
    "https": {
      "port": 443,
      "enabled": true
    }
  }
}

Accessing JSON Object Values

I've found two primary ways to access values in JSON objects:

  1. Dot Notation: Simple and readable, but doesn't work with keys containing spaces or special characters

    # Accessing nested values with dot notation
    - debug:
        msg: "The HTTP port is {{ server_config.services.http.port }}"
  2. Bracket Notation: More verbose but works with all key types

    # Accessing nested values with bracket notation
    - debug:
        msg: "The HTTPS port is {{ server_config['services']['https']['port'] }}"

JSON Arrays in Ansible

JSON arrays are ordered collections of values that work similarly to Python lists. Here's an example of a JSON array:

[
  "web01",
  "web02",
  "web03"
]

More commonly, you'll see arrays of objects, which are particularly useful for representing collections of similar items:

[
  {
    "server": "web01",
    "ip": "192.168.1.101"
  },
  {
    "server": "web02",
    "ip": "192.168.1.102"
  }
]

Accessing JSON Array Values

You can access array elements by their index (starting from 0):

- debug:
    msg: "The first server is {{ servers[0].server }} with IP {{ servers[0].ip }}"

Working with JSON in Variables

One of the first advanced techniques I learned was storing complex data structures in Ansible variables. This is particularly useful for configuration data:

---
# group_vars/webservers.yml
webserver_config:
  listen_port: 80
  document_root: "/var/www/html"
  virtual_hosts:
    - name: "example.com"
      root: "/var/www/example"
    - name: "blog.example.com"
      root: "/var/www/blog"
  modules:
    - "mod_ssl"
    - "mod_rewrite"

This YAML structure is internally converted to a Python dictionary (which is equivalent to a JSON object) and can be accessed just like JSON data.

Converting Between JSON and Other Formats

Ansible provides several filters for converting between JSON and other formats. These have been incredibly useful in my automation work:

Converting Strings to JSON (Parsing JSON)

When you receive JSON data as a string (for example, from an API response), you need to parse it:

- name: Get data from API
  uri:
    url: https://api.example.com/data
    method: GET
  register: api_response

- name: Parse JSON response
  set_fact:
    parsed_data: "{{ api_response.content | from_json }}"

Converting Variables to JSON Strings

When you need to send data to an API or write it to a file:

- name: Prepare API payload
  set_fact:
    api_payload: "{{ {'name': 'example', 'value': 42} | to_json }}"

- name: Send data to API
  uri:
    url: https://api.example.com/update
    method: POST
    body: "{{ api_payload }}"
    body_format: json

Real-world Example: Configuring Application Settings

Here's a practical example from my work. I needed to update a configuration file on both Linux and Windows servers based on environment-specific settings:

---
- name: Update application configuration
  hosts: all
  vars:
    base_config:
      app_name: "MyApp"
      version: "1.2.3"
      logging:
        level: "info"
        path: "{{ 'C:\\Logs' if ansible_os_family == 'Windows' else '/var/log' }}"
      database:
        type: "postgres"
        host: "db.example.com"
  
  tasks:
    - name: Merge with environment-specific settings
      set_fact:
        final_config: "{{ base_config | combine(env_specific_config, recursive=True) }}"
      vars:
        env_specific_config: "{{ lookup('file', 'configs/{{ environment }}.json') | from_json }}"
    
    # Windows-specific configuration
    - name: Update Windows config file
      win_copy:
        content: "{{ final_config | to_nice_json }}"
        dest: C:\Program Files\MyApp\config.json
      when: ansible_os_family == "Windows"
    
    # Linux-specific configuration
    - name: Update Linux config file
      copy:
        content: "{{ final_config | to_nice_json }}"
        dest: /etc/myapp/config.json
      when: ansible_os_family != "Windows"

In this example, I combine a base configuration with environment-specific overrides loaded from a JSON file.

Advanced JSON Manipulation with json_query

As I started working with more complex data structures, I discovered the power of json_query. This filter implements JMESPath, a query language for JSON. It's invaluable for extracting specific data from complex structures.

Installing JMESPath

Before using json_query, you need to install the JMESPath Python module on your Ansible controller:

pip install jmespath

Basic Filtering

Let's say you have a complex API response and want to extract specific information:

- name: Filter specific data from complex JSON
  debug:
    msg: "{{ complex_data | json_query('servers[*].name') }}"

This would extract all server names from an array of server objects.

Real-world Example: Processing API Results

Here's a practical example where I use json_query to transform data from an API into a more usable format:

---
- name: Process VM information
  hosts: localhost
  gather_facts: false
  
  tasks:
    - name: Get VM info from API
      uri:
        url: https://vcenter.example.com/api/vms
        method: GET
      register: vm_api_result
    
    - name: Parse API response
      set_fact:
        vm_data: "{{ vm_api_result.content | from_json }}"
    
    - name: Transform data for reporting
      set_fact:
        vm_report: "{{ vm_data | json_query('[].{name: item, status: instance.guest.guestState}') }}"
    
    - name: Show transformed data
      debug:
        var: vm_report

This example extracts just the VM names and their guest states from a more complex nested structure.

Windows Example: Processing Windows Features

For Windows automation, here's an example of using JSON manipulation to process Windows feature information:

---
- name: Process Windows features
  hosts: windows_servers
  
  tasks:
    - name: Get Windows feature info
      win_feature_info:
      register: feature_info
    
    - name: Get list of installed features
      set_fact:
        installed_features: "{{ feature_info.features | json_query('[?installed==`true`].{name: name, display_name: display_name}') }}"
    
    - name: Show installed features
      debug:
        var: installed_features

Advanced JSON Transformations

As your automation becomes more complex, you'll often need to transform JSON data in more sophisticated ways. Here's a sequence diagram showing how this typically works in an Ansible playbook:

This diagram illustrates the typical flow when working with JSON data in Ansible:

  1. Fetch data from an external source

  2. Parse the JSON string into Python objects

  3. Query and transform the data as needed

  4. Convert back to JSON if required (for APIs or file storage)

  5. Apply the transformed data to target systems

Practical Tips from Experience

After years of working with JSON in Ansible, here are some practical tips I've learned:

1. Use to_nice_json for Readable Output

When saving JSON to files or displaying it for debugging, use the to_nice_json filter instead of to_json to get nicely formatted, human-readable output:

- name: Write readable JSON to file
  copy:
    content: "{{ complex_data | to_nice_json }}"
    dest: /tmp/debug.json

2. Validate JSON Before Processing

When working with external sources, validate that the JSON is well-formed:

- name: Attempt to parse JSON
  set_fact:
    parsed_data: "{{ input_string | from_json }}"
  ignore_errors: yes
  register: parse_result

- name: Handle invalid JSON
  debug:
    msg: "The input contains invalid JSON"
  when: parse_result is failed

3. Use the default Filter for Missing Values

When accessing potentially missing keys, use the default filter to provide fallback values:

- name: Safely access value with default
  debug:
    msg: "The HTTP port is {{ config.services.http.port | default(80) }}"

4. Combine Filters for Complex Transformations

Chain filters together for more complex transformations:

- name: Complex transformation
  debug:
    msg: "{{ api_data | json_query('results[*].name') | map('regex_replace', '^srv-', '') | list }}"

Windows-specific JSON Handling

When working with Windows systems, I've found these JSON techniques particularly useful:

Working with PowerShell Output

PowerShell can output JSON directly, which makes it easy to integrate with Ansible:

- name: Run PowerShell command and get JSON output
  win_shell: Get-Process | Select-Object -First 5 | ConvertTo-Json
  register: process_output

- name: Parse PowerShell JSON output
  set_fact:
    processes: "{{ process_output.stdout | from_json }}"

- name: Show process names
  debug:
    msg: "{{ processes | json_query('[*].Name') }}"

Creating Windows Configuration Files

Many Windows applications use JSON for their configuration files. Here's how I generate them:

- name: Create Windows application config
  win_copy:
    content: |
      {{ {
        'settings': {
          'installDir': 'C:\\Program Files\\MyApp',
          'dataDir': 'C:\\ProgramData\\MyApp',
          'autoStart': true,
          'logging': {
            'level': 'info',
            'file': 'C:\\Logs\\MyApp\\app.log'
          }
        }
      } | to_nice_json }}
    dest: C:\ProgramData\MyApp\config.json

Conclusion

JSON has become integral to my Ansible workflows, enabling complex data handling that would otherwise require custom scripting. The ability to parse, query, transform, and generate JSON has been particularly valuable when working with modern infrastructure that relies heavily on APIs and configuration files.

Whether you're working with Linux or Windows systems, mastering JSON usage in Ansible will significantly expand what you can achieve with your automation. From simple variable manipulation to complex data transformations, the techniques I've shared here should help you handle virtually any JSON-related task in your automation journey.

As with many aspects of Ansible, I recommend starting simple and gradually incorporating more advanced techniques as your comfort level grows. Before you know it, you'll be transforming complex JSON structures with ease!


What JSON-related challenges have you encountered in your Ansible automation? I'd love to hear your experiences and solutions in the comments below!

Last updated