Ansible如何基于字典属性对字典列表进行嵌套重组
问题描述
我有一个包含Country/State/City/Street信息的字典列表,需要通过Parent_Id属性重组为层级嵌套结构,支持后续在指定City下搜索Street是否存在,不存在则新增。
原始数据结构
{"addresses": [{"Name": "North_America","Id": 10001,"Type": "Country","Parent_Id": 10000}, {"Name": "Maine","Id": 10011,"Type": "State","Parent_Id": 10001}, {"Name": "Colorado","Id": 10012,"Type": "State","Parent_Id": 10001}, {"Name": "Texas","Id": 10013,"Type": "State","Parent_Id": 10001}, {"Name": "Augusta","Id": 10101,"Type": "City","Parent_Id": 10011}, {"Name": "Portland","Id": 10102,"Type": "City","Parent_Id": 10011}, {"Name": "Denver","Id": 10103,"Type": "City","Parent_Id": 10012}, {"Name": "Austin","Id": 10104,"Type": "City","Parent_Id": 10013}, {"Name": "Houston","Id": 10105,"Type": "City","Parent_Id": 10013}, {"Name": "First_st","Id": 11001,"Type": "Street","Parent_Id": 10101}, {"Name": "Second_st","Id": 11002,"Type": "Street","Parent_Id": 10101}, {"Name": "First_st","Id": 11003,"Type": "Street","Parent_Id": 10102}, {"Name": "Second_st","Id": 11004,"Type": "Street","Parent_Id": 10102}, {"Name": "First_st","Id": 11005,"Type": "Street","Parent_Id": 10104}, {"Name": "Second_st","Id": 11006,"Type": "Street","Parent_Id": 10104}]}
预期嵌套结构
Country
State_1
City_1
Street_1
Street_2
City_2
Street_1
Street_2
State_2
City_1
Street_1
Street_2
City_2
Street_1
Street_2
尝试的Playbook及问题
我尝试用groupby操作构建结构,但未得到预期结果,多次分组还报错。比如执行grouped: "{{ map.addresses | groupby('Parent_Id') | groupby('Id') }}"时,提示第二个元素不存在。以下是我写的Playbook:
--- - name: Use groupby to create needed structure hosts: localhost gather_facts: false connection: local tasks: - include_vars: file: ../map.json name: map - name: set fact to group set_fact: grouped: "{{ map.addresses | groupby('Parent_Id') }}" - name: print grouped debug: var: grouped - name: set fact to re group set_fact: regrouped: "{{ regrouped | d([]) + [{ 'type': item.1 | map(attribute='Type')|list, 'name': item.1 | map(attribute='Name')|list|flatten }] }}" loop: "{{ grouped }}" - name: print regrouped debug: var: regrouped
解决方案
问题根源
直接用groupby('Parent_Id')只能得到按父ID分组的扁平结构,无法自动构建层级嵌套。后续二次groupby('Id')报错是因为groupby('Parent_Id')返回的是元组列表((父ID, 子元素列表)),元组没有Id属性,自然无法分组。
正确实现方式
我们需要先将所有地址项按Id建立索引,然后从顶层(Country)开始迭代构建嵌套结构。以下是可实现需求的Playbook:
--- - name: Build nested address structure hosts: localhost gather_facts: false connection: local tasks: - name: Load address data include_vars: file: ../map.json name: map - name: Create address index by Id set_fact: address_index: "{{ address_index | default({}) | combine({ item.Id: item }) }}" loop: "{{ map.addresses }}" - name: Build nested structure starting from Country set_fact: nested_addresses: "{{ nested_addresses | default([]) + [_build_nested(item)] }}" loop: "{{ map.addresses | selectattr('Type', 'equalto', 'Country') | list }}" vars: _build_nested: >- {%- set node = address_index[item.Id] -%} {%- set children = map.addresses | selectattr('Parent_Id', 'equalto', node.Id) | list -%} { "Name": "{{ node.Name }}", "Type": "{{ node.Type }}", "Id": "{{ node.Id }}", "Children": [ {%- for child in children -%} {{ _build_nested(child) }}{% if not loop.last %},{% endif %} {%- endfor -%} ] } - name: Print nested structure debug: var: nested_addresses - name: Example: Check if Street exists in Augusta and add if not set_fact: nested_addresses: "{{ _update_street(nested_addresses, 'Augusta', 'Third_st') }}" vars: _update_street: >- {%- macro find_city(nodes) -%} {%- for node in nodes -%} {%- if node.Type == 'City' and node.Name == city_name -%} {{ node }} {%- elif node.Children | length > 0 -%} {%- set found = find_city(node.Children) -%} {%- if found -%}{{ found }}{%- endif -%} {%- endif -%} {%- endfor -%} {%- endmacro -%} {%- set city = find_city(nodes) -%} {%- if city -%} {%- set street_exists = city.Children | selectattr('Name', 'equalto', street_name) | list | length > 0 -%} {%- if not street_exists -%} {%- set new_street = { "Name": street_name, "Type": "Street", "Id": (11000 + city.Children | length + 1) | string, "Parent_Id": city.Id } -%} {%- set updated_children = city.Children + [new_street] -%} {%- set updated_city = city | combine({'Children': updated_children}) -%} {%- macro update_nodes(nodes) -%} {%- for node in nodes -%} {%- if node.Id == updated_city.Id -%} {{ updated_city }} {%- elif node.Children | length > 0 -%} {{ node | combine({'Children': update_nodes(node.Children)}) }} {%- else -%} {{ node }} {%- endif -%} {%- if not loop.last -%},{%- endif -%} {%- endfor -%} {%- endmacro -%} [{{ update_nodes(nodes) }}] {%- else -%} {{ nodes }} {%- endif -%} {%- else -%} {{ nodes }} {%- endif -%} vars: city_name: "Augusta" street_name: "Third_st" - name: Print updated nested structure debug: var: nested_addresses
代码解释
- 创建地址索引:将所有地址项按
Id存入字典,方便快速查找父节点对应的子节点。 - 构建嵌套结构:从Country节点开始,递归遍历每个节点的子节点(通过
Parent_Id匹配),生成包含Children字段的嵌套字典。 - 搜索并新增Street:通过自定义宏
find_city定位目标City,检查指定Street是否存在,不存在则生成新的Street对象并更新嵌套结构。
最终效果
生成的nested_addresses会是如下层级结构(示例片段):
[ { "Name": "North_America", "Type": "Country", "Id": "10001", "Children": [ { "Name": "Maine", "Type": "State", "Id": "10011", "Children": [ { "Name": "Augusta", "Type": "City", "Id": "10101", "Children": [ {"Name": "First_st", "Type": "Street", "Id": "11001", "Children": []}, {"Name": "Second_st", "Type": "Street", "Id": "11002", "Children": []}, {"Name": "Third_st", "Type": "Street", "Id": "11003", "Children": []} ] } ] } ] } ]
内容的提问来源于stack exchange,提问作者piercjs
相关产品推荐
相关产品推荐

