4

I want to convert the values of a dictionary into strings and lowercase them, then map over the dict to join each item pair with an =, and then join the items with a ,.

Example input:

{"A": "Aardvark", "B": "Beaver", "C": "Cat"}

Desired output:

A=aardvark,B=beaver,C=cat

I actually don't care about the order that A, B, C arrive in, but I want to make sure that I the keys and values don't get mixed up in the end, i.e., no A=cat or whatever.

What I'm doing now is this:

{{ foo.keys() | zip(foo.values() | map("lower")) | map("join", "=") | join(",") }}

It works so far with the test data I have given it. However, I am not sure that this is guaranteed to be correct, nor do I think this is the most efficient or elegant way to solve this. Is there a better way to map the lower function over the values in the dict?

I tried using dict2items and specifying an attribute on the map function, but I get an error about an unexpected keyword arg when I do this, plus it's really ugly:

{{ (foo | dict2items | map("lower", attribute="value") | list | items2dict).items() | map("join", "=") | join(",") }}

Note: I am using Ansible 2.9 on Python 3.5 right now, but would really like a solution that works regardless of the Python version.

2rs2ts
  • 10,662
  • 10
  • 51
  • 95
  • 3
    I believe your use of `zip` is the best you're going to get due to the requirement to lowercase the values; you're also always welcome to use the imperative version `{% for ... %}` because the version of jinja2 used by ansible isn't very "functional programming" friendly, as you've experienced. Sometimes the imperative version is much, much clearer, even when more verbose – mdaniel Jan 06 '22 at 01:51
  • @mdaniel I considered it, but doing comma-separated values like that is always a pain – 2rs2ts Jan 06 '22 at 18:54

1 Answers1

1

You could use a custom filter.

Create a filter_plugins/ folder in your playbook folder (I have named the file myfilters.py and the filter cstring)

myfilters.py in folder filter_plugins/:

#!/usr/bin/python
class FilterModule(object):
    def filters(self):
        return {
            'cstring': self.cstring
        }
    
    def cstring(self, data):
        result = ""
        for k in data:
            result += (',' if result else '') + k + '=' + data[k].lower()
    
        return result

playbook:

- name: "tips1"
  hosts: localhost
  vars:
    foo: {"A": "Aardvark", "B": "Beaver", "C": "Cat"}
  tasks:
    - name: debug users      
      set_fact:
        result: "{{ foo | cstring }}"
  
    - name: display result     
      debug:
        var: result 

result:

ok: [localhost] => {
    "result": "A=aardvark,B=beaver,C=cat"
}

The advantage of using a custom filter is to do complex things or lot of operations with only one action.

Zeitounator
  • 38,476
  • 7
  • 53
  • 66
Frenchy
  • 16,386
  • 3
  • 16
  • 39