Get only key from nested object with Jinja filter

Viewed 594

I am using a Jinja filter in ansible to extract the value I need in the right format to process it.

This is the data in JSON format (I have shortened the output, usually there are much more variables per item and not all item have an IPv4 variable et all):

"interfaces": {
    "GigabitEthernet0": {
        "arp_timeout": "00:20:00",
        "arp_type": "arpa",
        "auto_negotiate": true,
        "bandwidth": 1000000
    },
    "GigabitEthernet0/0/0": {
        "arp_timeout": "00:20:00",
        "arp_type": "arpa",
        "auto_negotiate": true,
        "bandwidth": 10000
    },
    "GigabitEthernet0/0/0.3": {
        "arp_timeout": "04:00:00",
        "arp_type": "arpa",
        "bandwidth": 10000,
        "delay": 10,
        "description": "Private1 MPLS",
        "enabled": true,
        "encapsulations": {
            "encapsulation": "dot1q",
            "first_dot1q": "3"
        },
        "ipv4": {
            "10.10.84.2/30": {
                "ip": "10.10.84.2",
                "prefix_length": "30"
            }

That simple Jinja filer I use then to extract the information I need like the interface name and the IPv4:

[
{% for interface in interfaces if interfaces[interface]['ipv4'] is defined %}
{
"name": "{{ interface }}",
{% if interfaces[interface]['ipv4'] is defined %}
"prefix": "{{ interfaces[interface]['ipv4'] }}",
{% endif %}
"hostname": "{{ hostname }}"
}{{ ", " if not loop.last else "" }}
{% endfor %}
]

My problem is now that the parse data looks like this:

{
"name": "GigabitEthernet0/0/0.3",
"prefix": "{'10.10.84.2/30': {'ip': '10.10.84.2', 'prefix_length': '30'}}",
"hostname": "Horst1"
},

But I wanted to have only the key from the nested dict like this:

{
"name": "GigabitEthernet0/0/0.3",
"prefix": "10.10.84.2/30",
"hostname": "Horst1"
},

Isn't there a simple method in Jinja to get just the key from the nested object?

2 Answers

Here is for a possibly simpler template, using the for key, value in dict.items() construct:

[
{% for name, interface in interfaces.items() if interface.ipv4 is defined %}
  {
    "name": "{{ name }}",
    "prefix": "{{ (interface.ipv4.keys() | list).0 }}",
    "hostname": "{{ hostname }}"
  }{{ ", " if not loop.last }}
{% endfor %}
]

The keys() method is the one from Python, that return a view representing a list of the keys of that dictionary. Cast it back to a list and take the first element of it, an you should be good to go.

Another option would be to use dict2items, once again, take the first element of the generated list and get its key:

    "prefix": "{{ (interface.ipv4 | dict2items).0.key }}",

Neither Jinja nor iteration is needed. The expression below

name_prefix: "{{ interfaces|
                 dict2items|
                 selectattr('value.ipv4', 'defined')|
                 json_query('[].{name: key,
                                 prefix: value.ipv4.keys(@)|[0]}') }}"

creates the list of the dictionaries

name_prefix:
  - name: GigabitEthernet0/0/0.3
    prefix: 10.10.84.2/30

Notes

  • The attribute ipv4 might be defined in more interfaces, hence the result is a list. Take the first item if you want to.

  • You can combine the dictionaries if you want to. For example

horst_default:
  hostname: Horst1
  domain: foo.bar
horst: "{{ name_prefix|map('combine', horst_default)|list }}"

gives

horst:
  - domain: foo.bar
    hostname: Horst1
    name: GigabitEthernet0/0/0.3
    prefix: 10.10.84.2/30
  • You can use the variable in Jinja if you want to. For example
    - debug:
        msg: |
          {{ horst|first|to_nice_json }}

gives

  msg: |-
    {
        "domain": "foo.bar",
        "hostname": "Horst1",
        "name": "GigabitEthernet0/0/0.3",
        "prefix": "10.10.84.2/30"
    }
Related