Is there any way to prevent ansible changing my JSON stored in a variable?

Edit: As suggested by Vladimir Botka here is a minimal example.

I have this template file json.j2

{"foo":"{{ var }}"}

which I would like to render into a variable and insert that variable into another template

- name render it
  ansible.builtin.template:
    src: another.j2
    [...]
  vars:
    json_from_template: "{{ lookup('template', 'json.j2') }}"

As it seems ansible interprets the json from json.j2 and changes it to

{ 'foo': 'var_value' }

But "because of reasons" I need the json unchanged, no spaces, no single quotes, as specified in the json.j2.

Can I tell ansible to keep the result of json.j2 as a raw string?

I already tried !unsafe, but this

  vars:
    json_from_template: !unsafe "{{ lookup('template', 'json.j2') }}"

only renders the raw lookup command without any templating at all, and this

  vars:
    json_from_template: "{{ !unsafe lookup('template', 'json.j2') }}"

does not work at all.


Solution 1:

Note: In the snippets below, the callback yaml is used to display the output to the screen.

shell> ansible-config dump | grep DEFAULT_STDOUT_CALLBACK
DEFAULT_STDOUT_CALLBACK(/home/admin/.ansible.cfg) = yaml

Ansible, when templating Jinja2, tries and converts strings that look like python data structures to python data structures. For example

shell> cat json.j2 
{"foo":"{{ var }}"}
    - set_fact:
        json_from_template: "{{ lookup('template', 'json.j2') }}"
      vars:
        var: var_value
    - debug:
        var: json_from_template
    - debug:
        var: json_from_template|type_debug

gives

  json_from_template:
    foo: var_value

  json_from_template|type_debug: dict

You can see the type of the variable json_from_template is the dictionary. What you need is a string. You have to explicitly declare it

    - set_fact:
        json_from_template: "{{ lookup('template', 'json.j2')|string }}"
      vars:
        var: var_value
    - debug:
        var: json_from_template|string
    - debug:
        var: json_from_template|type_debug

gives

  json_from_template|string: |-
    {"foo":"var_value"}

  json_from_template|type_debug: str

It seems that this "magic" conversion works for valid JSON only. See the variable test_2 below. Other variables remain strings despite the fact that they are valid YAML dictionaries

  vars:
    val1: foo
    test1: |-
      {"key1": "foo"}
    test2: |-
      {"key1": "{{ val1 }}"}
    test3: |-
      {"key1": {{ val1 }}}
    test4: |-
      {key1: {{ val1 }}}
    test5: |-
      key1: {{ val1 }}

  tasks:
    - debug:
        msg: |
          test1: {{ test1|type_debug }}
          test2: {{ test2|type_debug }}
          test3: {{ test3|type_debug }}
          test4: {{ test4|type_debug }}
          test5: {{ test5|type_debug }}

    - debug:
        msg: |
          test1|from_yaml: {{ test1|from_yaml|type_debug }}
          test3|from_yaml: {{ test3|from_yaml|type_debug }}
          test4|from_yaml: {{ test4|from_yaml|type_debug }}
          test5|from_yaml: {{ test5|from_yaml|type_debug }}

gives

  msg: |-
    test1: AnsibleUnicode
    test2: dict
    test3: str
    test4: str
    test5: str

  msg: |-
    test1|from_yaml: dict
    test3|from_yaml: dict
    test4|from_yaml: dict
    test5|from_yaml: dict

The "magic" conversion applies to Jinja2 templates in Ansible expressions only. For example

    - debug:
        msg: "{{ json_from_template|string }}"
    - debug:
        msg: "{{ json_from_template }}"

gives

  msg: |-
    {"foo":"var_value"}

  msg:
    foo: var_value

Jinja2 templates inside the template module evaluate always to strings. For example

shell> cat another.j2 
{{ json_from_template|string }}
{{ json_from_template }}
    - template:
        src: another.j2
        dest: output.txt

gives

shell> cat output.txt 
{"foo":"var_value"}

{"foo":"var_value"}