Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Ansible Playbook JSON Configuration Strategies

Ansible playbooks are primarily written in YAML, a human-readable data serialization format. However, in modern automation workflows, you often need to interact with data in JSON format. This could be fetching data from APIs, processing output from commands, defining complex nested variables, or configuring applications that expect JSON input.

Integrating JSON effectively into your Ansible playbooks requires understanding how Ansible handles data transformation and manipulation. This article explores common strategies for working with JSON within your playbooks.

Handling JSON Data with Filters

Ansible provides built-in filters powered by Jinja2 to easily convert between YAML/Python objects and JSON strings. The two primary filters for this are to_json and from_json.

The to_json Filter

The to_json filter converts an Ansible variable (which is essentially a Python data structure like a dictionary or list) into a JSON formatted string. This is useful when you need to pass data to a command-line tool, an API call, or write it to a file in JSON format.

You can optionally pass arguments like indent to make the output human-readable.

Example: Using to_json

---
- name: Demonstrate to_json filter
  hosts: localhost
  gather_facts: false

  vars:
    my_data:
      name: "Ansible User"
      roles: ["developer", "sysadmin"]
      settings:
        verbose: true
        level: 5

  tasks:
    - name: Output data as a JSON string
      debug:
        msg: "{{ my_data | to_json }}"

    - name: Output data as a pretty-printed JSON string
      debug:
        msg: "{{ my_data | to_json(indent=2) }}"

    - name: Create a JSON file from the variable
      copy:
        content: "{{ my_data | to_json(indent=2) }}"
        dest: /tmp/my_config.json

The first debug task will output a compact JSON string. The second will output a formatted, indented string, which is often easier to read. The copy task demonstrates how to write this JSON string to a file.

The from_json Filter

The from_json filter (or its alias from_yaml, as YAML is a superset of JSON) takes a JSON formatted string and converts it into an Ansible variable (a Python dictionary or list). This is essential when you read JSON from a file, receive it from an API response, or capture it from command output.

Example: Using from_json

---
- name: Demonstrate from_json filter
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Simulate receiving JSON data from a command/API
      set_fact:
        json_output_string: '{"status": "success", "data": {"id": 123, "items": ["apple", "banana"]}}'

    - name: Convert JSON string to Ansible variable
      set_fact:
        parsed_data: "{{ json_output_string | from_json }}"

    - name: Access elements from the parsed JSON data
      debug:
        msg: "Status: {{ parsed_data.status }}, First item: {{ parsed_data.data.items[0] }}"

After applying from_json, you can navigate the resulting Ansible variable using standard dot or bracket notation, just like any other variable in your playbook.

Storing JSON Data in Variables

While you can store JSON as a string and use from_json, Ansible (being YAML-based) is perfectly capable of representing nested data structures directly.

Often, a JSON structure can be directly translated into equivalent YAML structure within a playbook's vars section or a separate variable file. Ansible will handle this nested YAML as the corresponding Python dictionary or list, which is compatible with JSON structure.

Example: JSON-like structure in Ansible Vars

---
- name: JSON structure stored directly in vars
  hosts: localhost
  gather_facts: false

  vars:
    user_config:
      user:
        name: "Charlie"
        id: 456
        preferences:
          theme: "dark"
          notifications:
            email: true
            sms: false
      permissions:
        - role: "editor"
          level: "high"
        - role: "viewer"
          level: "low"

  tasks:
    - name: Access nested data
      debug:
        msg: "User name: {{ user_config.user.name }}, Email notifications: {{ user_config.user.preferences.notifications.email }}"

    - name: Convert this structure to JSON string
      debug:
        msg: "{{ user_config | to_json(indent=2) }}"

This approach is generally cleaner and more readable for complex static configuration data than embedding large JSON strings.

Reading JSON from Files

You can store complex JSON configuration data in separate .json files and read them into your playbook using the include_vars or vars_files keywords. Ansible will automatically parse the JSON content into a variable.

Example: Reading JSON from a file (config.json)

Contents of config.json:

{
  "app": {
    "name": "WebApp",
    "version": "1.0.0",
    "enabled_features": ["auth", "logging"]
  },
  "database": {
    "host": "db.example.com",
    "port": 5432
  }
}

Playbook snippet:

---
- name: Read config from JSON file
  hosts: localhost
  gather_facts: false

  vars_files:
    - config.json # Ansible automatically parses this as JSON/YAML

  tasks:
    - name: Access data read from the JSON file
      debug:
        msg: "App name: {{ app.name }}, DB Host: {{ database.host }}"

    - name: Convert the loaded data back to JSON (optional, for verification)
      debug:
        msg: "{{ vars | to_json(indent=2) }}" # 'vars' includes variables from vars_files

When using vars_files or include_vars, Ansible is smart enough to detect the file format (YAML or JSON) and load it accordingly, making this a straightforward way to manage external JSON configuration.

Querying JSON Data with json_query

For complex JSON data structures, navigating manually with dot/bracket notation can become cumbersome, especially when dealing with lists or needing to filter data. The json_query filter, based on JMESPath, provides a powerful way to query JSON structures.

Example: Using json_query

---
- name: Demonstrate json_query filter
  hosts: localhost
  gather_facts: false

  vars:
    complex_data:
      users:
        - id: 1
          name: "Alice"
          active: true
          roles: ["admin", "user"]
        - id: 2
          name: "Bob"
          active: false
          roles: ["user"]
        - id: 3
          name: "Charlie"
          active: true
          roles: ["editor", "user"]
      settings:
        default_role: "user"

  tasks:
    - name: Get names of all users
      debug:
        msg: "{{ complex_data | json_query('users[*].name') }}" # [ "Alice", "Bob", "Charlie" ]

    - name: Get users who are active
      debug:
        msg: "{{ complex_data | json_query('users[?active == `true`]') | to_json(indent=2) }}"
      # Outputs list of user objects where active is true

    - name: Get names of users who have the 'admin' role
      debug:
        msg: "{{ complex_data | json_query('users[?contains(roles, `'admin'`)].name') }}" # [ "Alice" ]

    - name: Get default setting
      debug:
        msg: "{{ complex_data | json_query('settings.default_role') }}" # "user"

JMESPath syntax takes some learning, but it's incredibly powerful for extracting exactly the data you need from complex JSON structures, making your playbooks cleaner and more robust.

Best Practices

  • Use from_json/from_yaml for parsing: Always convert incoming JSON strings to Ansible variables using these filters before attempting to access their elements.
  • Prefer YAML structure for static data: If your JSON data is part of your playbook's static configuration, define it directly using YAML's native nested structure instead of embedding JSON strings.
  • Store large/complex JSON in separate files: Use vars_files or include_vars to keep your playbooks clean when dealing with substantial JSON configurations.
  • Leverage json_query for complex data extraction: Invest time in learning JMESPath if you frequently work with nested or list-heavy JSON outputs.
  • Handle potential errors: JSON operations can fail if the input string is invalid or the query path doesn't exist. Use conditional checks or error handling (e.g., failed_when, ignore_errors, using default(omit) or | d()/| d([])) where appropriate.

Common Pitfalls

  • Not parsing JSON strings: Trying to access elements of a JSON string directly (e.g., "{...}".status) will fail. Always use | from_json first.
  • YAML vs. JSON syntax: While YAML can represent JSON, be mindful of indentation and special characters in YAML strings if manually embedding JSON strings. Using the | to_json filter is safer for generating JSON strings.
  • Complex queries without json_query: Deeply nested access like variable['key1']['key2'][0]['subkey'] becomes hard to read and maintain quickly. Use json_query for anything beyond basic access.

Conclusion

Effectively managing JSON data is a crucial skill for modern Ansible automation. By utilizing Ansible's built-in filters like to_json, from_json, and json_query, and by structuring your variable data appropriately, you can seamlessly integrate JSON into your playbooks, making them capable of interacting with a wider range of systems and data sources.

Understanding these strategies allows you to build more flexible, powerful, and maintainable automation workflows that can handle complex configuration scenarios.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool