如何比较两个遵循架构的JSON文件?

How to compare two JSON files that adhere to a schema?

提问人:njminchin 提问时间:11/8/2023 最后编辑:njminchin 更新时间:11/11/2023 访问量:72

问:

我有 JSON(用于 Ignition SCADA)在系统中定义“标签”,它本质上定义了一个树结构,其中结束树节点是标签本身,具有定义它们的配置,子树元素是文件夹。每个文件夹都有一个名为“tags”的键,其中包含其中包含的任何标签或文件夹的列表。

定义标记的 JSON 中最重要的部分是它的路径,它由 JSON 中沿它所在的文件夹路径的每个“name”字段组成,类似于文件的文件路径。路径的元素用“/”分隔,例如,对于下面的 TagA,其标记路径为“StackOverflow/FolderA/TagA”。

下面是 Ignition 设计器中的树:

Tags Displayed in Ignition Tag Browser GUI

下面是表示这些标记的 JSON:

{
  "name": "StackOverflow",
  "tagType": "Folder",
  "tags": [
    {
      "name": "FolderA",
      "tagType": "Folder",
      "tags": [
        {
          "valueSource": "memory",
          "dataType": "Int8",
          "name": "TagA",
          "tooltip": "This is tag a",
          "value": 15,
          "tagType": "AtomicTag",
          "engUnit": "%"
        },
        {
          "valueSource": "memory",
          "dataType": "Int8",
          "name": "TagB",
          "tooltip": "This is tag b",
          "value": 98,
          "tagType": "AtomicTag",
          "engUnit": "%"
        }
      ]
    }
  ]
}

如果我通过以下方式更改这些标签/文件夹:

  • 添加新标签,StackOverflow/FolderA/TagC
  • 更改值StackOverflow/FolderA/TagA.tooltip
  • 更改值StackOverflow/FolderA/TagA.value
  • 删除StackOverflow/FolderA/TagB

我想获得一份报告,作为我可以导出到 Excel 中的更改列表,例如:

[{'tagpath': 'StackOverflow/FolderA/TagC', 'changedFrom': None, 'changedTo': 'added'},
{'tagpath': 'StackOverflow/FolderA/TagA.tooltip', 'changedFrom': 'This is tag a', 'changedTo': 'This is tag a with changes'},
{'tagpath': 'StackOverflow/FolderA/TagA.value', 'changedFrom': 15, 'changedTo': 59},
{'tagpath': 'StackOverflow/FolderA/TagB', 'changedFrom': None, 'changedTo': 'deleted'}
]

以下是更改 JSON:

{
  "name": "StackOverflow",
  "tagType": "Folder",
  "tags": [
    {
      "name": "FolderA",
      "tagType": "Folder",
      "tags": [
        {
          "valueSource": "memory",
          "dataType": "Int8",
          "name": "TagC",
          "tooltip": "This is tag a with changes",
          "value": 59,
          "tagType": "AtomicTag",
          "engUnit": "%"
        },
        {
          "valueSource": "memory",
          "dataType": "Int8",
          "name": "TagA",
          "tooltip": "This is tag a with changes",
          "value": 59,
          "tagType": "AtomicTag",
          "engUnit": "%"
        }
      ]
    }
  ]
}

enter image description here

我不确定如何使用 Python 最好地比较这两个文件。我可以使用简单的 for 循环并手动比较 json 并跟踪差异来将一些东西组合在一起,但我想知道是否有更好更复杂的方法?

json python-3.x

评论


答:

1赞 SuNing 11/8/2023 #1

可能有些包会有用。如果你不想使用它,你可以使用迭代器来代替一个接一个。

from jsondiff import diff
json1 = {
    "name": "Bob",
    "age": 10,
    "sex": "male"
}
json2 = {
    "name": "Alice",
    "age": 10,
    "sex": "female"
}

difference1 = diff(json1, json2)
difference2 = diff(json2, json1)
print(difference1)
print(difference2)

结果: {'name': '爱丽丝', '': '女性'} {'name': '鲍勃', '': '男'}

我认为您可以使用它来构建您的更改日志。

评论

0赞 Community 11/8/2023
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。
0赞 njminchin 11/9/2023
嗯,虽然这不适用于比较整个 JSON 文件,但这引发了一个想法:我从未考虑过将其用于每个标签,而是至少获得标签级别的差异。然后,我需要编写自己的差异函数来检查标签是否被删除/添加,但这应该不会太糟糕。我会试一试,谢谢
2赞 Laurent Verweijen 11/11/2023 #2

这个问题可以用littletree(我是作者)来解决。

假设原始数据和修改后的数据分别作为嵌套字典存储在 和 中originalmodified

from littletree import Node

original_tree = Node.from_dict(original, identifier_name="name", children_name="tags")
modified_tree = Node.from_dict(modified, identifier_name="name", children_name="tags")

# Collect changes in a list
changes = []
for diff_node in original_tree.compare(modified_tree).iter_tree():
    diff_data = diff_node.data
    if not diff_data:
        continue  # Data was the same
    
    if "self" not in diff_data:
        changes.append({"tagpath": str(diff_node.path), "from": None, "to": "added"})
    elif "other" not in diff_data:
        changes.append({"tagpath": str(diff_node.path), "from": None, "to": "removed"})
    else:
        original_data, modified_data = diff_data["self"], diff_data["other"]
        for key, original_value in original_data.items():
            modified_value = modified_data[key]
            if original_value != modified_value:
                changes.append({"tagpath": f"{diff_node.path}.{key}",
                                "from": original_value,
                                "to": modified_value})

for change in changes:
    print(change)

结果如下所示:

{'tagpath': '/StackOverflow/FolderA/TagA.tooltip', 'from': 'This is tag a', 'to': 'This is tag a but changed'}
{'tagpath': '/StackOverflow/FolderA/TagA.value', 'from': 15, 'to': 16}
{'tagpath': '/StackOverflow/FolderA/TagB', 'from': None, 'to': 'removed'}
{'tagpath': '/StackOverflow/FolderA/TagC', 'from': None, 'to': 'added'}

评论

0赞 njminchin 11/13/2023
这看起来很酷,但是当我运行它时,我没有看到正确的结果。这是我得到的:{'tagpath': '/StackOverflow/FolderA/TagA.tagType', 'from': 'Folder', 'to': 'AtomicTag'} {'tagpath': '/StackOverflow/FolderA/TagB', 'from': None, 'to': 'removed'} {'tagpath': '/StackOverflow/FolderA/TagC', 'from': None, 'to': 'added'}
1赞 Laurent Verweijen 11/13/2023
最近已修复,但我看到修复程序尚未发布。我现在刚刚发布了它。
0赞 njminchin 11/13/2023
太棒了,我现在让它工作了,谢谢!:)我在更改部分对其进行了一些修改,因为它不处理另一个部分中不存在的键,但这就是我更改的全部内容。超级酷,谢谢!
0赞 njminchin 11/14/2023
我不确定在哪里最好问这个问题,但我想将您的库用于另一个 json 模式差异。在本例中,是每个节点的 prop 的子键,例如 .这可以使用吗?identifier_namemetameta.name
0赞 Laurent Verweijen 11/15/2023
也不知道该问哪里。随意在 github.com/lverweijen/littletree/issues 上创建问题 它不是开箱即用的,但这可能会有所帮助: ''' def to_tree(data): “”“标识符存储在 node[”meta“][”name“].”“” identifier = data[“meta”][“name”] children = [to_tree(child) for child in data.pop(“tags”, [])] return Node(data, identifier=identifier, children=children) '''