Ansible from_json and to_json — Parse and Generate JSON
Introduction
Ansible's from_json and to_json filters convert between JSON strings and native data structures. Use from_json to parse API responses and command output. Use to_json to generate JSON for API calls and config files. Both are essential for working with REST APIs and JSON-based tools.
Quick Reference
# Parse JSON string → dict/list
- set_fact:
data: "{{ json_string | from_json }}"
# Convert dict/list → JSON string
- debug:
msg: "{{ my_dict | to_json }}"
# Pretty-printed JSON
- debug:
msg: "{{ my_dict | to_nice_json }}"
from_json — Parse JSON Strings
- name: Parse JSON from command output
ansible.builtin.command: cat /etc/myapp/config.json
register: config_raw
changed_when: false
- name: Convert to dictionary
ansible.builtin.set_fact:
config: "{{ config_raw.stdout | from_json }}"
- name: Access parsed values
ansible.builtin.debug:
msg: "Database host: {{ config.database.host }}"
Parse API Response
- name: Call REST API
ansible.builtin.uri:
url: https://api.example.com/v1/servers
return_content: true
register: api_response
# uri module auto-parses JSON, but if you have a raw string:
- name: Parse manually if needed
ansible.builtin.set_fact:
servers: "{{ api_response.content | from_json }}"
- name: List server names
ansible.builtin.debug:
msg: "{{ servers | map(attribute='name') | list }}"
Parse JSON from Shell Output
- name: Get Docker container info
ansible.builtin.command: docker inspect mycontainer
register: docker_info
changed_when: false
- name: Parse container IP
ansible.builtin.set_fact:
container_ip: "{{ (docker_info.stdout | from_json)[0].NetworkSettings.IPAddress }}"
- name: Show IP
ansible.builtin.debug:
msg: "Container IP: {{ container_ip }}"
to_json — Generate JSON Strings
- name: Create JSON config file
ansible.builtin.copy:
content: "{{ app_config | to_nice_json }}"
dest: /etc/myapp/config.json
vars:
app_config:
database:
host: db.example.com
port: 5432
name: production
cache:
type: redis
host: redis.example.com
ttl: 3600
Send JSON to API
- name: Create resource via API
ansible.builtin.uri:
url: https://api.example.com/v1/resources
method: POST
body: "{{ payload | to_json }}"
body_format: json
headers:
Authorization: "Bearer {{ api_token }}"
vars:
payload:
name: "{{ resource_name }}"
type: compute
region: us-east-1
tags:
environment: production
team: platform
to_nice_json — Pretty Print
- name: Readable JSON output
ansible.builtin.debug:
msg: "{{ complex_data | to_nice_json }}"
# Output:
# {
# "name": "myapp",
# "version": "2.1.0",
# "dependencies": [
# "redis",
# "postgresql"
# ]
# }
# Control indentation
- debug:
msg: "{{ data | to_nice_json(indent=2) }}"
from_yaml and to_yaml
# Parse YAML string
- set_fact:
config: "{{ yaml_string | from_yaml }}"
# Generate YAML
- copy:
content: "{{ data | to_nice_yaml }}"
dest: /etc/myapp/config.yml
# Convert between formats
- copy:
content: "{{ (json_string | from_json) | to_nice_yaml }}"
dest: /tmp/converted.yml
Common Patterns
Filter JSON Array
- name: Filter servers by status
ansible.builtin.set_fact:
running_servers: "{{ (api_response.content | from_json) | selectattr('status', 'equalto', 'running') | list }}"
Extract Nested Values
- name: Get all IPs from JSON response
ansible.builtin.set_fact:
all_ips: "{{ (api_response.content | from_json).results | map(attribute='ip_address') | list }}"
Merge JSON Objects
- name: Merge default and custom config
ansible.builtin.set_fact:
final_config: "{{ default_config | combine(custom_config, recursive=True) }}"
vars:
default_config:
log_level: info
port: 8080
features:
auth: true
cache: false
custom_config:
log_level: debug
features:
cache: true
JSON Patch (Modify and Write Back)
- name: Read existing config
ansible.builtin.slurp:
src: /etc/myapp/config.json
register: config_file
- name: Parse and modify
ansible.builtin.set_fact:
config: "{{ config_file.content | b64decode | from_json | combine({'version': '2.0'}) }}"
- name: Write updated config
ansible.builtin.copy:
content: "{{ config | to_nice_json }}\n"
dest: /etc/myapp/config.json
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Expecting value: line 1 | Input isn't valid JSON | Verify with echo '$var' | jq . |
dict object has no attribute | Key doesn't exist in parsed JSON | Use default() filter: data.key | default('') |
list object has no attribute | JSON is array, not object | Access with index: data[0].key |
to_json outputs single quotes | Jinja2 native string | Use | string | to_json |
| Unicode escape in output | Default encoding | Use ensure_ascii=False: to_json(ensure_ascii=False) |
Best Practices
- Use
to_nice_jsonfor config files — human-readable - Use
to_jsonfor API calls — compact, efficient - Handle missing keys with
default()— prevent failures on optional fields - Validate before parsing — check command succeeded before
from_json - Use
urimodule's built-in parsing — it auto-parses JSON responses into.jsonattribute - Add trailing newline —
{{ data | to_nice_json }}\nfor POSIX files
Conclusion
Use from_json to parse command output, API responses, and file contents into Ansible dictionaries. Use to_json or to_nice_json to generate JSON for API calls and config files. Combine with selectattr, map, and combine filters for powerful data transformations. Always handle missing keys with the default() filter.