如何合并导出为 json 的 Firefox 书签(字典结构中的字典)

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

我正在尝试创建一个新字典,该字典是由现有字典创建的,该字典是通过一个函数创建的,该函数尝试合并从备份 Firefox 书签的 json 文件派生的两个字典。

第一个 json 书签字典:

json1={'guid': 'root________', 'title': '', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697130233008000, 'id': 1, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'placesRoot', 'children': [{'guid': 'menu________', 'title': 'menu', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697130233008000, 'id': 2, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'bookmarksMenuFolder', 'children': [{'guid': '9GEAbdFPVBqv', 'title': 'Getting started - mypy 1.5.1 documentation', 'index': 0, 'dateAdded': 1696274666207000, 'lastModified': 1696274666207000, 'id': 16, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://mypy.readthedocs.io/en/stable/getting_started.html'}, {'guid': 'PDKXoMPpSKZ9', 'title': 'testFolder2', 'index': 1, 'dateAdded': 1697130042452000, 'lastModified': 1697130183178000, 'id': 18, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'jbP4ff424REs', 'title': 'Secure Coding with Python', 'index': 0, 'dateAdded': 1697130058445000, 'lastModified': 1697130058445000, 'id': 19, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://devopedia.org/secure-coding-with-python'}, {'guid': 'bZSAKQe67MEP', 'title': 'testSubFolder', 'index': 1, 'dateAdded': 1697130074677000, 'lastModified': 1697130183178000, 'id': 20, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': '0U5O4Rw6M3M5', 'title': 'Typer', 'index': 0, 'dateAdded': 1697130183178000, 'lastModified': 1697130183178000, 'id': 21, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://typer.tiangolo.com/'}]}]}, {'guid': '-j27AP1Cwt0O', 'title': 'testFolder1', 'index': 2, 'dateAdded': 1697130021758000, 'lastModified': 1697130233008000, 'id': 17, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'Wb3-R2DDT8Ip', 'title': 'Welcome to Click — Click Documentation (8.1.x)', 'index': 0, 'dateAdded': 1697130230240000, 'lastModified': 1697130230240000, 'id': 22, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://click.palletsprojects.com/en/8.1.x/'}]}, {'guid': 'VDTmkniLNlvN', 'title': 'Mozilla Firefox', 'index': 3, 'dateAdded': 1688927107386000, 'lastModified': 1697129949344000, 'id': 7, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'vUwrKuzYfywC', 'title': 'Get Help', 'index': 0, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 8, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/products/firefox', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/products/firefox'}, {'guid': 'mKpEl6U5Pppr', 'title': 'Customize Firefox', 'index': 1, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 9, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize'}, {'guid': 'Rw167-bbT1fR', 'title': 'Get Involved', 'index': 2, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 10, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/contribute/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/contribute/'}, {'guid': 'stHPEtkREVvD', 'title': 'About Us', 'index': 3, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 11, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/about/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/about/'}]}]}, {'guid': 'toolbar_____', 'title': 'toolbar', 'index': 1, 'dateAdded': 1688927106926000, 'lastModified': 1696274298931000, 'id': 3, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'toolbarFolder', 'children': [{'guid': '690YbVlf5eS_', 'title': 'Getting Started', 'index': 0, 'dateAdded': 1688927107484000, 'lastModified': 1688927107484000, 'id': 12, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/firefox/central/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/firefox/central/'}, {'guid': 'YJ_gjoZ6Wwj1', 'title': 'json — JSON encoder and decoder — Python 3.11.5 documentation', 'index': 1, 'dateAdded': 1696274298931000, 'lastModified': 1696274298931000, 'id': 13, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python.org/3/library/json.html'}]}, {'guid': 'unfiled_____', 'title': 'unfiled', 'index': 3, 'dateAdded': 1688927106926000, 'lastModified': 1688927107332000, 'id': 5, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'unfiledBookmarksFolder'}, {'guid': 'mobile______', 'title': 'mobile', 'index': 4, 'dateAdded': 1688927106942000, 'lastModified': 1688927107332000, 'id': 6, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'mobileFolder'}]}

第二个 json 书签字典:

json2={'guid': 'root________', 'title': '', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697131416648000, 'id': 1, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'placesRoot', 'children': [{'guid': 'menu________', 'title': 'menu', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697131416648000, 'id': 2, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'bookmarksMenuFolder', 'children': [{'guid': '9GEAbdFPVBqv', 'title': 'Getting started - mypy 1.5.1 documentation', 'index': 0, 'dateAdded': 1696274666207000, 'lastModified': 1696274666207000, 'id': 16, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://mypy.readthedocs.io/en/stable/getting_started.html'}, {'guid': 'FNxknWay_xr8', 'title': "Command-line Applications — The Hitchhiker's Guide to Python", 'index': 1, 'dateAdded': 1697130502023000, 'lastModified': 1697130502023000, 'id': 23, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python-guide.org/scenarios/cli/'}, {'guid': 'PDKXoMPpSKZ9', 'title': 'testFolder2', 'index': 2, 'dateAdded': 1697130042452000, 'lastModified': 1697131416648000, 'id': 18, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'bZSAKQe67MEP', 'title': 'testSubFolder', 'index': 0, 'dateAdded': 1697130074677000, 'lastModified': 1697131416648000, 'id': 20, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'm6MBObvLXgt6', 'title': 'The Python Fire Guide - Python Fire', 'index': 0, 'dateAdded': 1697130663668000, 'lastModified': 1697130663668000, 'id': 28, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://google.github.io/python-fire/guide/'}, {'guid': 'fl2vHRLT-RJY', 'title': 'Typer', 'index': 1, 'dateAdded': 1697131416648000, 'lastModified': 1697131416648000, 'id': 30, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://typer.tiangolo.com/'}]}, {'guid': 'Z2khP-DX2nJU', 'title': 'testsubF2', 'index': 1, 'dateAdded': 1697130537074000, 'lastModified': 1697130642695000, 'id': 26, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'ZHUGVs2ZYUiA', 'title': 'argparse — Parser for command-line options, arguments and sub-commands — Python 3.12.0 documentation', 'index': 0, 'dateAdded': 1697130642695000, 'lastModified': 1697130642695000, 'id': 27, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python.org/3/library/argparse.html'}]}, {'guid': '3RP_KOI4Pq0q', 'title': 'plac · PyPI', 'index': 2, 'dateAdded': 1697130513781000, 'lastModified': 1697130513781000, 'id': 24, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://pypi.org/project/plac/'}]}, {'guid': '-j27AP1Cwt0O', 'title': 'testFolder1', 'index': 3, 'dateAdded': 1697130021758000, 'lastModified': 1697130520562000, 'id': 17, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'Wb3-R2DDT8Ip', 'title': 'Welcome to Click — Click Documentation (8.1.x)', 'index': 0, 'dateAdded': 1697130230240000, 'lastModified': 1697130230240000, 'id': 22, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://click.palletsprojects.com/en/8.1.x/'}, {'guid': 'zuT4_jp_Rj5l', 'title': 'cliff – Command Line Interface Formulation Framework — cliff 4.3.1.dev12 documentation', 'index': 1, 'dateAdded': 1697130520562000, 'lastModified': 1697130520562000, 'id': 25, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.openstack.org/cliff/latest/'}]}, {'guid': 'LjrKYDavnU7w', 'title': 'Generating Command-Line Interfaces (CLI) with Fire in Python', 'index': 4, 'dateAdded': 1697130696550000, 'lastModified': 1697130696550000, 'id': 29, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://stackabuse.com/generating-command-line-interfaces-cli-with-fire-in-python/'}, {'guid': 'VDTmkniLNlvN', 'title': 'Mozilla Firefox', 'index': 5, 'dateAdded': 1688927107386000, 'lastModified': 1697129949344000, 'id': 7, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'vUwrKuzYfywC', 'title': 'Get Help', 'index': 0, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 8, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/products/firefox', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/products/firefox'}, {'guid': 'mKpEl6U5Pppr', 'title': 'Customize Firefox', 'index': 1, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 9, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize'}, {'guid': 'Rw167-bbT1fR', 'title': 'Get Involved', 'index': 2, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 10, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/contribute/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/contribute/'}, {'guid': 'stHPEtkREVvD', 'title': 'About Us', 'index': 3, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 11, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/about/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/about/'}]}]}, {'guid': 'toolbar_____', 'title': 'toolbar', 'index': 1, 'dateAdded': 1688927106926000, 'lastModified': 1696274298931000, 'id': 3, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'toolbarFolder', 'children': [{'guid': '690YbVlf5eS_', 'title': 'Getting Started', 'index': 0, 'dateAdded': 1688927107484000, 'lastModified': 1688927107484000, 'id': 12, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/firefox/central/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/firefox/central/'}, {'guid': 'YJ_gjoZ6Wwj1', 'title': 'json — JSON encoder and decoder — Python 3.11.5 documentation', 'index': 1, 'dateAdded': 1696274298931000, 'lastModified': 1696274298931000, 'id': 13, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python.org/3/library/json.html'}]}, {'guid': 'unfiled_____', 'title': 'unfiled', 'index': 3, 'dateAdded': 1688927106926000, 'lastModified': 1688927107332000, 'id': 5, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'unfiledBookmarksFolder'}, {'guid': 'mobile______', 'title': 'mobile', 'index': 4, 'dateAdded': 1688927106942000, 'lastModified': 1688927107332000, 'id': 6, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'mobileFolder'}]}

我的想法是通过执行以下操作来简化这两个词典的合并:

def has_url(diction):
    if "uri" in diction:
        return True
    else:
        return False


def merge_dicts(dictmain, dict2):
    return {**dict2, **dictmain}



def get_bm_path(bookmarks):
    urls = {}

    def bm_path(x, name=""):
        if type(x) is dict:
            name = name + x.get("guid") + "/"
            if has_url(x):
                urls[name] = [f"uri_{x.get('guid')}", dict((k, x[k]) for k in x.keys() if k not in "children")]

            else:
                urls[name] = [f"folder_{x.get('guid')}", dict((k, x[k]) for k in x.keys() if k not in "children")]
            bm_path(x=x.get("children"), name=name)
        elif type(x) is list:
            for i, a in enumerate(x):
                bm_path(a, name=name)

    bm_path(bookmarks)
    return urls

然后运行:

merged_dict = merge_dicts(get_bm_path(json1), get_bm_path(json2))

我得到了一个合并的字典,其中的键类似于文件夹/文件结构,以免在合并时丢失书签的父/子结构的跟踪。

然后我需要使函数从合并的字典派生出 Firefox 使用的实际 json 结构。

最小(也许)可重现的示例:

主要词典:

{'main_folder/': {'id': 'main_folder', 'ad': 'what'}, 'main_folder/subfolder1/': {'id': 'subfolder1', 'ad': 'what'}, 'main_folder/subfolder1/9GEAbdFPVBqv/': {'id': '9GEAbdFPVBqv', 'ad': 'what1'}, 
'main_folder/subfolder1/eaXY8H5Y1cJ_/': {'id': 'eaXY8H5Y1cJ_', 'ad': 'what2'},
'main_folder/subfolder1/eaXY8H5Y1cJ_/9p2UFp7-qcEt/': {'id': '9p2UFp7-qcEt', 'ad': 'what3'},
'main_folder/subfolder1/fijaCypbmbU1/': {'id': 'fijaCypbmbU1', 'ad': 'what4'}, 
'main_folder/subfolder2/': {'id': 'subfolder2', 'ad': 'what7'}} 

生成的字典应该是这样的:

{'id': 'main_folder', 'ad': 'what', 
    'children':[
     {'id': 'subfolder1', 'ad': 'what', 
        'children':[
         {'id': '9GEAbdFPVBqv', 'ad': 'what1'},
         {'id': 'eaXY8H5Y1cJ_', 'ad': 'what2',
             'children': [{'id': '9p2UFp7-qcEt', 'ad': 'what3'}]},
         {'id': 'fijaCypbmbU1', 'ad': 'what4'}
         ]
      },
      {'id': 'subfolder2', 'ad': 'what7'}
      ]
}

生成的字典为父/子字典/列表中的每个元素(子文件夹)创建树状结构。 我找不到一种方法来递归地将文件的每个“层”减少到子文件夹。

python recursion parent-child
2个回答
1
投票

为了能够轻松地合并这些,需要有一个实现〜整个结构中的每个

dict
都是一个可能有一个
dict
键的
children
。由于该键是“也许”,我们至少可以得出,每个
dict
只是一个
dict
,我们应该同等对待它们。

实现此目的的一个很好的方法是使用递归。我们可以输入整个结构并让它将子字典反馈回自身,因此,一切都会经历完全相同的过程。

代码的工作原理如下:

当前要合并的

data
字典与要合并到的
targ
字典同时传递。如果
targ
字典为空,则会立即更新为
data
字典内容。然后迭代
children
字典中的所有
data
。在每次迭代中,都会进行检查以在
targ
上找到具有匹配
guid
的子项。匹配项将作为
children
列表中匹配项的索引返回。如果未找到匹配项,则索引默认为
-1
。这是因为,如果没有找到匹配项,我们将在列表末尾附加一个空字典。最后,
data['children']
的当前子级和
targ['children']
索引处的字典被传回到函数中,并且该过程重新开始。

这只是一堆“我的孩子的指导与你的孩子的指导匹配吗?”如果没有,则根据需要制作尽可能多的新词典来容纳新孩子。如果有匹配的孩子,则发送匹配项以查看其孩子是否匹配。一直持续到没有更多数据为止。

触发

update
线相当于说。 “我没有这个孩子,但现在我有了。”,唯一的例外是第一次打电话时
r_merge
。在这种情况下,这就像是在说“我什么都没有,现在我与第一个书签对象的所有内容相同”。这也意味着在第一个
update
之后,直到到达第二个书签对象之前,什么都不会发生。它只是遍历完全匹配的子列表。

解决方案
#merge data to targ by eid recursing on r_key
def r_merge(data:dict, targ:dict, eid:str, r_key:str) -> None:
    #guard clause
    if not data.get(eid): raise ValueError
    
    #update if empty
    if not targ: targ.update(data)
        
    #process children
    for entry in data.get(r_key, []):
        #find match index else default to -1
        i = next((i for i, ent in enumerate(targ[r_key]) if ent[eid] == entry[eid]), -1)
        
        #no match, prime with empty dict
        if i<0: targ[r_key].append(dict())
        
        #recursive merge
        r_merge(entry, targ[r_key][i], eid, r_key)
使用
import json

json1={'guid': 'root________', 'title': '', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697130233008000, 'id': 1, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'placesRoot', 'children': [{'guid': 'menu________', 'title': 'menu', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697130233008000, 'id': 2, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'bookmarksMenuFolder', 'children': [{'guid': '9GEAbdFPVBqv', 'title': 'Getting started - mypy 1.5.1 documentation', 'index': 0, 'dateAdded': 1696274666207000, 'lastModified': 1696274666207000, 'id': 16, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://mypy.readthedocs.io/en/stable/getting_started.html'}, {'guid': 'PDKXoMPpSKZ9', 'title': 'testFolder2', 'index': 1, 'dateAdded': 1697130042452000, 'lastModified': 1697130183178000, 'id': 18, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'jbP4ff424REs', 'title': 'Secure Coding with Python', 'index': 0, 'dateAdded': 1697130058445000, 'lastModified': 1697130058445000, 'id': 19, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://devopedia.org/secure-coding-with-python'}, {'guid': 'bZSAKQe67MEP', 'title': 'testSubFolder', 'index': 1, 'dateAdded': 1697130074677000, 'lastModified': 1697130183178000, 'id': 20, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': '0U5O4Rw6M3M5', 'title': 'Typer', 'index': 0, 'dateAdded': 1697130183178000, 'lastModified': 1697130183178000, 'id': 21, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://typer.tiangolo.com/'}]}]}, {'guid': '-j27AP1Cwt0O', 'title': 'testFolder1', 'index': 2, 'dateAdded': 1697130021758000, 'lastModified': 1697130233008000, 'id': 17, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'Wb3-R2DDT8Ip', 'title': 'Welcome to Click — Click Documentation (8.1.x)', 'index': 0, 'dateAdded': 1697130230240000, 'lastModified': 1697130230240000, 'id': 22, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://click.palletsprojects.com/en/8.1.x/'}]}, {'guid': 'VDTmkniLNlvN', 'title': 'Mozilla Firefox', 'index': 3, 'dateAdded': 1688927107386000, 'lastModified': 1697129949344000, 'id': 7, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'vUwrKuzYfywC', 'title': 'Get Help', 'index': 0, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 8, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/products/firefox', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/products/firefox'}, {'guid': 'mKpEl6U5Pppr', 'title': 'Customize Firefox', 'index': 1, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 9, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize'}, {'guid': 'Rw167-bbT1fR', 'title': 'Get Involved', 'index': 2, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 10, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/contribute/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/contribute/'}, {'guid': 'stHPEtkREVvD', 'title': 'About Us', 'index': 3, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 11, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/about/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/about/'}]}]}, {'guid': 'toolbar_____', 'title': 'toolbar', 'index': 1, 'dateAdded': 1688927106926000, 'lastModified': 1696274298931000, 'id': 3, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'toolbarFolder', 'children': [{'guid': '690YbVlf5eS_', 'title': 'Getting Started', 'index': 0, 'dateAdded': 1688927107484000, 'lastModified': 1688927107484000, 'id': 12, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/firefox/central/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/firefox/central/'}, {'guid': 'YJ_gjoZ6Wwj1', 'title': 'json — JSON encoder and decoder — Python 3.11.5 documentation', 'index': 1, 'dateAdded': 1696274298931000, 'lastModified': 1696274298931000, 'id': 13, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python.org/3/library/json.html'}]}, {'guid': 'unfiled_____', 'title': 'unfiled', 'index': 3, 'dateAdded': 1688927106926000, 'lastModified': 1688927107332000, 'id': 5, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'unfiledBookmarksFolder'}, {'guid': 'mobile______', 'title': 'mobile', 'index': 4, 'dateAdded': 1688927106942000, 'lastModified': 1688927107332000, 'id': 6, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'mobileFolder'}]}
json2={'guid': 'root________', 'title': '', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697131416648000, 'id': 1, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'placesRoot', 'children': [{'guid': 'menu________', 'title': 'menu', 'index': 0, 'dateAdded': 1688927106926000, 'lastModified': 1697131416648000, 'id': 2, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'bookmarksMenuFolder', 'children': [{'guid': '9GEAbdFPVBqv', 'title': 'Getting started - mypy 1.5.1 documentation', 'index': 0, 'dateAdded': 1696274666207000, 'lastModified': 1696274666207000, 'id': 16, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://mypy.readthedocs.io/en/stable/getting_started.html'}, {'guid': 'FNxknWay_xr8', 'title': "Command-line Applications — The Hitchhiker's Guide to Python", 'index': 1, 'dateAdded': 1697130502023000, 'lastModified': 1697130502023000, 'id': 23, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python-guide.org/scenarios/cli/'}, {'guid': 'PDKXoMPpSKZ9', 'title': 'testFolder2', 'index': 2, 'dateAdded': 1697130042452000, 'lastModified': 1697131416648000, 'id': 18, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'bZSAKQe67MEP', 'title': 'testSubFolder', 'index': 0, 'dateAdded': 1697130074677000, 'lastModified': 1697131416648000, 'id': 20, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'm6MBObvLXgt6', 'title': 'The Python Fire Guide - Python Fire', 'index': 0, 'dateAdded': 1697130663668000, 'lastModified': 1697130663668000, 'id': 28, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://google.github.io/python-fire/guide/'}, {'guid': 'fl2vHRLT-RJY', 'title': 'Typer', 'index': 1, 'dateAdded': 1697131416648000, 'lastModified': 1697131416648000, 'id': 30, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://typer.tiangolo.com/'}]}, {'guid': 'Z2khP-DX2nJU', 'title': 'testsubF2', 'index': 1, 'dateAdded': 1697130537074000, 'lastModified': 1697130642695000, 'id': 26, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'ZHUGVs2ZYUiA', 'title': 'argparse — Parser for command-line options, arguments and sub-commands — Python 3.12.0 documentation', 'index': 0, 'dateAdded': 1697130642695000, 'lastModified': 1697130642695000, 'id': 27, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python.org/3/library/argparse.html'}]}, {'guid': '3RP_KOI4Pq0q', 'title': 'plac · PyPI', 'index': 2, 'dateAdded': 1697130513781000, 'lastModified': 1697130513781000, 'id': 24, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://pypi.org/project/plac/'}]}, {'guid': '-j27AP1Cwt0O', 'title': 'testFolder1', 'index': 3, 'dateAdded': 1697130021758000, 'lastModified': 1697130520562000, 'id': 17, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'Wb3-R2DDT8Ip', 'title': 'Welcome to Click — Click Documentation (8.1.x)', 'index': 0, 'dateAdded': 1697130230240000, 'lastModified': 1697130230240000, 'id': 22, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://click.palletsprojects.com/en/8.1.x/'}, {'guid': 'zuT4_jp_Rj5l', 'title': 'cliff – Command Line Interface Formulation Framework — cliff 4.3.1.dev12 documentation', 'index': 1, 'dateAdded': 1697130520562000, 'lastModified': 1697130520562000, 'id': 25, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.openstack.org/cliff/latest/'}]}, {'guid': 'LjrKYDavnU7w', 'title': 'Generating Command-Line Interfaces (CLI) with Fire in Python', 'index': 4, 'dateAdded': 1697130696550000, 'lastModified': 1697130696550000, 'id': 29, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://stackabuse.com/generating-command-line-interfaces-cli-with-fire-in-python/'}, {'guid': 'VDTmkniLNlvN', 'title': 'Mozilla Firefox', 'index': 5, 'dateAdded': 1688927107386000, 'lastModified': 1697129949344000, 'id': 7, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'children': [{'guid': 'vUwrKuzYfywC', 'title': 'Get Help', 'index': 0, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 8, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/products/firefox', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/products/firefox'}, {'guid': 'mKpEl6U5Pppr', 'title': 'Customize Firefox', 'index': 1, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 9, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize', 'type': 'text/x-moz-place', 'uri': 'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize'}, {'guid': 'Rw167-bbT1fR', 'title': 'Get Involved', 'index': 2, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 10, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/contribute/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/contribute/'}, {'guid': 'stHPEtkREVvD', 'title': 'About Us', 'index': 3, 'dateAdded': 1688927107386000, 'lastModified': 1688927107386000, 'id': 11, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/about/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/about/'}]}]}, {'guid': 'toolbar_____', 'title': 'toolbar', 'index': 1, 'dateAdded': 1688927106926000, 'lastModified': 1696274298931000, 'id': 3, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'toolbarFolder', 'children': [{'guid': '690YbVlf5eS_', 'title': 'Getting Started', 'index': 0, 'dateAdded': 1688927107484000, 'lastModified': 1688927107484000, 'id': 12, 'typeCode': 1, 'iconUri': 'fake-favicon-uri:https://www.mozilla.org/firefox/central/', 'type': 'text/x-moz-place', 'uri': 'https://www.mozilla.org/firefox/central/'}, {'guid': 'YJ_gjoZ6Wwj1', 'title': 'json — JSON encoder and decoder — Python 3.11.5 documentation', 'index': 1, 'dateAdded': 1696274298931000, 'lastModified': 1696274298931000, 'id': 13, 'typeCode': 1, 'type': 'text/x-moz-place', 'uri': 'https://docs.python.org/3/library/json.html'}]}, {'guid': 'unfiled_____', 'title': 'unfiled', 'index': 3, 'dateAdded': 1688927106926000, 'lastModified': 1688927107332000, 'id': 5, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'unfiledBookmarksFolder'}, {'guid': 'mobile______', 'title': 'mobile', 'index': 4, 'dateAdded': 1688927106942000, 'lastModified': 1688927107332000, 'id': 6, 'typeCode': 2, 'type': 'text/x-moz-place-container', 'root': 'mobileFolder'}]} 

results = dict()          #for storing results
datas   = (json1, json2)  #gather dicts for iteration

for data in datas:
    r_merge(data, results, 'guid', 'children') #recursive merge
    
#print results        
print(json.dumps(results, indent=4))  

1
投票

这是一种可能的解决方案,使用

defaultdict
:

dct = {
    "main_folder/": ["folder_main_folder", {"id": "main_folder", "ad": "what"}],
    "main_folder/subfolder1/": [
        "folder_subfolder1",
        {"id": "subfolder1", "ad": "what"},
    ],
    "main_folder/subfolder1/9GEAbdFPVBqv/": [
        "file_9GEAbdFPVBqv",
        {"id": "9GEAbdFPVBqv", "ad": "what1"},
    ],
    "main_folder/subfolder1/eaXY8H5Y1cJ_/": [
        "folder_eaXY8H5Y1cJ_",
        {"id": "eaXY8H5Y1cJ_", "ad": "what2"},
    ],
    "main_folder/subfolder1/eaXY8H5Y1cJ_/9p2UFp7-qcEt/": [
        "file_9p2UFp7-qcEt",
        {"id": "9p2UFp7-qcEt", "ad": "what3"},
    ],
    "main_folder/subfolder1/fijaCypbmbU1/": [
        "file_fijaCypbmbU1",
        {"id": "fijaCypbmbU1", "ad": "what4"},
    ],
    "main_folder/subfolder2/": [
        "folder_subfolder2",
        {"id": "subfolder2", "ad": "what7"},
    ],
}

from collections import defaultdict


def tree():
    return defaultdict(tree)


out = tree()

for keys, (name, data) in zip((k.strip("/").split("/") for k in dct), dct.values()):
    head = out
    for k in keys:
        head = head[k]
    head.update(data)


def convert(dct):
    out = {}
    for k, v in dct.items():
        if k in {"id", "ad"}:
            out[k] = v
        else:
            out.setdefault("children", [])
            out["children"].append(convert(v))
    return out


out = convert(out)
print(out["children"][0])

打印:

{
    "id": "main_folder",
    "ad": "what",
    "children": [
        {
            "id": "subfolder1",
            "ad": "what",
            "children": [
                {"id": "9GEAbdFPVBqv", "ad": "what1"},
                {
                    "id": "eaXY8H5Y1cJ_",
                    "ad": "what2",
                    "children": [{"id": "9p2UFp7-qcEt", "ad": "what3"}],
                },
                {"id": "fijaCypbmbU1", "ad": "what4"},
            ],
        },
        {"id": "subfolder2", "ad": "what7"},
    ],
}
© www.soinside.com 2019 - 2024. All rights reserved.