在Python中合并深层JSON文件

2024-06-02 07:21:00 发布

您现在位置:Python中文网/ 问答频道 /正文

我有两个JSON文件,一个包含一个完全定义的对象和多个嵌套级别,另一个包含同一对象的精简版本,只列出需要更改的元素

文件1示例

{
  "toplevel": {
    "value": {
      "settings": [
        {
          "name": "A Default Value",
          "region": "US",
          "inner": {
            "name": "Another Default",
            "setting": "help"
          }
        }
      ]
    }
  }
}

文件2示例

{
  "toplevel": {
    "value": {
      "settings": [
        {
          "name": "A Real Value",
          "inner": {
            "name": "Another Real Value",
          }
        }
      ]
    }
  }
}

我想将文件2中的更新合并到文件1中

我的输出应该是

{
  "toplevel": {
    "value": {
      "settings": [
        {
          "name": "A Real Value",
          "region": "US",
          "inner": {
            "name": "Another Real Value",
            "setting": "help"
          }
        }
      ]
    }
  }
}

到目前为止我已经试过了

f1 = json_load(file1)
f2 = json_load(file2)
f1['toplevel']['value']['settings'][0].update(f2['toplevel']['value']['settings'][0].items())

它非常适合顶级项目,但显然它覆盖了整个“内部”对象,删除了其中的“设置”键

有没有办法遍历整个树并只替换非字典值?除了json和集合之外,我没有访问外部库的权限(对于有序dict)


Tags: 文件对象namejsondefault示例settingsvalue
1条回答
网友
1楼 · 发布于 2024-06-02 07:21:00

这取决于你想要什么

解决方案1

如果只想用新字典替换所有值,可以使用以下选项:

result = {**file_1, **file_2}  

from pprint import pprint
pprint(result)

这将导致:

{'toplevel': {'value': {'settings': [{'inner': {'name': 'Another Real Value'},
                                      'name': 'A Real Value'}]}}}

或者,您可以使用

file_1.update(file_2)

pprint(file_1)

这将导致相同的结果,但将更新file_1

解决方案2

如果您只想更新嵌套中的特定键,而保留所有其他值不变,那么可以使用递归来实现这一点。在您的示例中,您使用的是dictliststr值。因此,我将使用相同的类型构建递归

def update_dict(original, update):
    for key, value in update.items():

        # Add new key values
        if key not in original:
            original[key] = update[key]
            continue

        # Update the old key values with the new key values
        if key in original:
            if isinstance(value, dict):
                update_dict(original[key], update[key])
            if isinstance(value, list):
                update_list(original[key], update[key])
            if isinstance(value, (str, int, float)):
                original[key] = update[key]
    return original
def update_list(original, update):
    # Make sure the order is equal, otherwise it is hard to compare the items.
    assert len(original) == len(update), "Can only handle equal length lists."

    for idx, (val_original, val_update) in enumerate(zip(original, update)):
        if not isinstance(val_original, type(val_update)):
            raise ValueError(f"Different types! {type(val_original)}, {type(val_update)}")
        if isinstance(val_original, dict):
            original[idx] = update_dict(original[idx], update[idx])
        if isinstance(val_original, (tuple, list)):
            original[idx] = update_list(original[idx], update[idx])
        if isinstance(val_original, (str, int, float)):
            original[idx] = val_update
    return original

上述内容可能有点难以理解,但我会尽力解释。 有两种方法,一种是合并两个字典,另一种是尝试合并两个列表

合并词典

为了合并这两个字典,我检查了更新字典的所有键和值,因为这可能是两者中较小的一个

第一个块将新键放入原始字典,这是更新开始时不在原始字典中的值

第二个块正在更新嵌套值。在这里,我区分了三种情况:

  1. 如果该值是另一个dict,则再次运行字典合并,但更深一层
  2. 如果值是list(或tuple),请运行列表合并函数
  3. 如果值是str(或intfloat),则用更新的值替换原始值

合并列表

这比字典要复杂一点,因为列表没有可以比较的顺序或键。因此,我不得不做出一个沉重的假设,即list更新将始终包含相同的元素,请参阅如何处理具有多个元素的list的限制

由于lists的长度相同,我可以假设列表的索引是匹配的。现在,为了检查所有值是否相同,我们必须执行以下操作:

  1. 确保值类型相同,否则我们将抛出一个错误,因为我不确定如何处理这种情况
  2. 如果值是字典,请使用字典合并
  3. 如果值为list(或tuple),则列表合并
  4. 如果值为str(或intfloat),则就地重写原始值

结果

使用:

from pprint import pprint

pprint(update_dict(file_1, file_2))

最终结果将是:

{'toplevel': {'value': {'settings': [{'inner': {'name': 'Another Real Value',
                                                'setting': 'help'},
                                      'name': 'A Real Value',
                                      'region': 'US'}]}}}

请注意,与第一个解决方案相比,值'setting': 'help''region': 'US'}现在仍在原始字典中

限制

由于相同的长度限制,如果不想更新列表中的元素,则必须传递相同的元素类型,但为空

有关如何忽略列表更新的示例:

... {'settings': [
          {}                      # do not update the first element.
          {'name': 'A new name'}  # update second element.
       ]
    }

相关问题 更多 >