【问题标题】:Replace values of specific sub dicts in nested json based on condition根据条件替换嵌套 json 中特定子字典的值
【发布时间】:2020-11-24 21:47:39
【问题描述】:

我有一个相当嵌套的 json 结构,我想循环并根据条件修改 ID 值。理想情况下,不需要键名,因为它们可以是可变的。我的想法是从顶层获取 Id 值,然后在整个 json 文件中搜索它并修改所有匹配的值,因为 ID 值始终相同。修改只是向其添加“_001”。但条件应该是它只在“类型”的子句中完成:“A”。 请参阅下面我正在处理的结构的示例 json:

{
    "name1": "test1",
    "Id": "12345678",
    "data": [
        {
            "type": "A",
            "Id1": "12345678",
            "level2": {
                "name2": "test2",
                "Id2": "12345678",
                "level3": {
                    "name3": "test3",
                    "Id3": "12345678"
                },
                "Edit": [
                    {
                        "Id": "12345678",
                        "Bla": "XXXXXXXXXX"
                    },
                    {
                        "Id": "12345678",
                        "Bla": "XXXXXXXXX"
                    }
                ]
            }
        },
        {
            "type": "B",
            "id": "12345678",
            "data": {
                "Id": "12345678",
                "Name": "test6"
            }
        }
    ]
}

如果“type”等于“A”,我想遍历这个 json 并替换 level1 内每个实例中的 Id 值,否则不需要遍历那个特定的 dict,这意味着不应该修改 ID。理想情况下,我会在顶层检查这个 id 值,然后按值而不是键进行过滤,因为键可能会有所不同(参见最低级别,其中称为“source_id”)。因此,在示例 json 中,子字典中带有“type”:“A”的所有 ID 都应该被替换,而带有“type”:“B”的 ID 应该保留。 作为输出,我想以相同的格式返回 json,只是更改了 ID。

到目前为止,我的代码仍然依赖于对键名的了解,并且没有捕获字典“编辑”列表中的 Id,因为我无法弄清楚在基于上述类型过滤器的嵌套字典:

data = json.load(open("test.json"))
curr_id = data['Id']
new_id = curr_id + '_001')
for item in data['data']:
    if item['type'] != 'B':                    
        item['Id1'] = new_id
        item['level2']['Id2'] = new_id
        item['level2']['level3']['Id3'] = new_id

【问题讨论】:

    标签: json python-3.x


    【解决方案1】:

    我会使用递归实现:

    • 循环data['data']
    • 对于那里的每个项目,检查我们是否需要替换 ID
    • 替换该项目/级别中的 ID
    • 检查该级别是否有“编辑”并替换那里的 ID
    • 通过检查当前项目的dict 中是否有level<current_level + 1> 键来移动到下一个级别

    这种方法的优点是您可以拥有可变数量的级别(例如级别 4 和 5 是可能的),并且可以正确处理它们。

    代码:

    import json
    
    def replace_id_recursively(item, old_id, new_id, level=1):
        """
        This method replaces the key on the current level
        and calls itself to replace lower levels
        """
        # This is an early-return: If the current
        # type is not "A", move on (only for level 1)
        # NOTE: is important to check level first since
        # other levels do not have 'type'
        if level == 1 and item['type'] != 'A':
            return
    
        # Change this level only if it has the old key
        key = f"Id{level}"
        print(f"Checking level {level} and key {key}")
        if item[key] != old_id:
            return
    
        item[key] = new_id
    
        # Check if this level has "Edit" - could be
        # split in another function
        if "Edit" in item:
            print(f" - Handling Edit in level {level}")
            for edit_item in item["Edit"]:
                edit_item["Id"] = new_id
    
        # Finally, before moving to the next item in
        # the list, change any children
        child = f"level{level + 1}"
        if child in item:
            replace_id_recursively(item[child], old_id, new_id, level + 1)
    
    # MAIN
    data = json.load(open("/tmp/data.json"))
    old_id = data['Id']
    new_id = f"{old_id}_001"
    
    # For each item, check and replace IDs recursively
    for item in data['data']:
        replace_id_recursively(item, old_id, new_id, 1)
    
    print(json.dumps(data, indent=4))
    

    输出:

    $ python3 ./tmp/so.py 
    Checking level 1 and key Id1
    Checking level 2 and key Id2
     - Handling Edit in level 2
    Checking level 3 and key Id3
    {
        "name1": "test1",
        "Id": "12345678",
        "data": [
            {
                "type": "A",
                "Id1": "12345678_001",
                "level2": {
                    "name2": "test2",
                    "Id2": "12345678_001",
                    "level3": {
                        "name3": "test3",
                        "Id3": "12345678_001"
                    },
                    "Edit": [
                        {
                            "Id": "12345678_001",
                            "Bla": "XXXXXXXXXX"
                        },
                        {
                            "Id": "12345678_001",
                            "Bla": "XXXXXXXXX"
                        }
                    ]
                }
            },
            {
                "type": "B",
                "id": "12345678",
                "data": {
                    "Id": "12345678",
                    "Name": "test6"
                }
            }
        ]
    }
    

    注释/假设:

    • 只有当旧的/当前的 ID 符合我们的预期时,ID 才会被替换
    • 每个项目可能有也可能没有“编辑”
    • “编辑”始终是一个列表
    • “编辑”ID 键始终为“Id”
    • “编辑”中的 old_id 未选中,但可以根据需要轻松添加

    【讨论】:

      【解决方案2】:

      这是一个简短的解决方案,用your_dict = replace_recurse(your_dict)调用它

      def replace_recurse(it):
          if isinstance(it, list):
              return [
                  replace_recurse(entry) if entry.get('type', 'A') == 'A' else entry
                  for entry in it
              ]
          if isinstance(it, dict):
              return {
                  k: (v + '_001' if k.startswith('Id') else replace_recurse(v))
                  for k, v in it.items()
              }
          return it
      

      这种方法会盲目地递归到它找到的任何字典或列表中。除非递归在看到定义为'type' 的字典不是'A' 时停止。在递归过程中,'_001' 会附加到每个以Id 开头的字段,因此这里是IdId1Id2

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-23
        • 2021-03-29
        • 2019-09-06
        • 2014-08-19
        • 2021-07-15
        相关资源
        最近更新 更多