根据条件替换嵌套json中特定子字典的值

问题描述 投票:0回答:2

我有一个相当嵌套的 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"
            }
        }
    ]
}

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

到目前为止,我的代码仍然依赖于键名称的知识,并且没有捕获“编辑”字典列表中的 Id,因为我无法弄清楚如何在嵌套字典中搜索像“12345678”这样的 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
python json python-3.x
2个回答
1
投票

我会采用递归实现:

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

这种方法的优点是你可以有可变数量的级别(例如级别 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未被选中,但如果需要的话很容易添加

0
投票

这是一个简短的解决方案,用

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
开头的每个字段,因此此处为
Id
Id1
Id2

© www.soinside.com 2019 - 2024. All rights reserved.