jsonfilteransiblejmespathjson-query

Ansible json_query move dictionary key/value to subkey that is a list


I'm trying to edit a dictionary in ansible with a one line json_query (and/or other filters), but something is wrong and I can't figure it out. I have this structure (with example data)

seinfeld:
  - name: Jerry
    jobs:
      - comedian
    friends: 
      - name: Elaine
      - name: Kramer
  - name: George
    jobs:
      - yankees
    friends:
      - name: Jerry
      - name: Susan

And I need to create a new structure like this

new_list:
    - main: Jerry
      name: Elaine
    - main: Jerry
      name: Kramer
    - main: George
      name: Jerry
    - main: George
      name: Susan

The name key is added to every element of friends key and flatten it out to a new list. I already made some progress mixing jmespath map and merge, but at least one key is always null or invalid, some of random tests are [].merge({friends: friends},{main: name}) [*].map(&merge({main: name}, @), friends)


Solution

  • There are more options for getting the data. For example,

      tmp: "{{ seinfeld | json_query('[].[name, friends[].name]') }}"
    
      tmp: "{{ seinfeld | map(attribute='name') |
               zip(seinfeld | map(attribute='friends')
                            | map('map', attribute='name')) }}"
    

    give the same list

      tmp:
      - - Jerry
        - - Elaine
          - Kramer
      - - George
        - - Jerry
          - Susan
    

    Now, you would need to map the function product. Unfortunately, this is not possible. The filter json_query doesn't help you with the list of lists either. So, this is a dead end.

    The only option is a Jinja template

      new_list: |
        {% filter from_yaml %}
        {% for name in tmp %}
        {% for friend in name.1 %}
        - {main: {{ name.0 }}, name: {{ friend }}}
        {% endfor %}
        {% endfor %}
        {% endfilter %}
    

    gives what you want

      new_list:
      - main: Jerry
        name: Elaine
      - main: Jerry
        name: Kramer
      - main: George
        name: Jerry
      - main: George
        name: Susan
    

    But, you can easily iterate the list with subelements. For example,

        - debug:
            msg: "main: {{ item.0.name }}, name: {{ item.1.name }}"
          loop: "{{ seinfeld | subelements('friends') }}"
    

    gives (abridged)

      msg: 'main: Jerry, name: Elaine'
      msg: 'main: Jerry, name: Kramer'
      msg: 'main: George, name: Jerry'
      msg: 'main: George, name: Susan'
    

    The decision of whether you need new_list is up to you.


    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        seinfeld:
          - name: Jerry
            jobs:
              - comedian
            friends: 
              - name: Elaine
              - name: Kramer
          - name: George
            jobs:
              - yankees
            friends:
              - name: Jerry
              - name: Susan
    
        tmp1: "{{ seinfeld | json_query('[].[name, friends[].name]') }}"
        tmp2: "{{ seinfeld | map(attribute='name') |
                  zip(seinfeld | map(attribute='friends')
                               | map('map', attribute='name')) }}"
        new_list: |
          {% filter from_yaml %}
          {% for name in tmp1 %}
          {% for friend in name.1 %}
          - {main: {{ name.0 }}, name: {{ friend }}}
          {% endfor %}
          {% endfor %}
          {% endfilter %}
    
      tasks:
    
        - debug:
            var: tmp1
        - debug:
            var: tmp2
        - debug:
            var: new_list
    
        - debug:
            msg: "main: {{ item.0.name }}, name: {{ item.1.name }}"
          loop: "{{ seinfeld | subelements('friends') }}"