用另一个字典中对应的值替换嵌套字典的键(当两个字典的键相同时),如果一个键的值是字典列表,则也要进行替换。

4
我有两个字典,dict1和dict2,我想要构建一个新的字典(或操作dict1),其中键值对为(dict2的值:dict1的值[其中dict1和dict2的键相同]),键的值可能是字典列表(如您将在输入示例中看到)。 输入为:
dict1 = {"key1":{"key3":"value1","key2":"value2","key4":{"key5":"value3","key6":{"key7":"value4","key8":{"key9":"value5","key10":"value6","key55":"value7"}},"key11":{"key12":"value8","key13":"value9"},"key14":[{"key15":"value10","key16":"value11","key17":"value12"},{"key15":"value13","key16":"value14","key17":"value15"}]}}}

dict2 = {"key1":"ab","key2":"bc","key3":"cd","key4":"de","key5":"ef","key6":"fg","key7":"gh","key8":"hi","key9":"ij","key10":"jk","key55":"kl","key11":"lm","key12":"mn","key13":"no","key14":"op","key15":"pq","key16":"qr","key17":"qs"}

我的函数是

def walk(dict1, dict2):
    output = {}
    for key, value in dict1.iteritems():

        if isinstance(value, dict):
            output[dict2[key]] = walk(value, dict2)
        elif isinstance(value, list):
            output[dict2[key]] = walk_list(value, dict2)
        else:
            output[dict2[key]] = value
    return output

def walk_list(sublist, dict2):
    output = []
    for i in sublist:

        if isinstance(i, dict):
            output = walk(i, dict2)
        elif isinstance(value, list):
            output = walk_list(i, dict2)
        else:
            output.append((key, value))
    return output

output = walk(dict1, dict2)
output = json.dumps(output)
print output

我得到的输出是:
 {"ab": {"de": {"lm": {"mn": "value8", "no": "value9"}, "ef": "value3", "fg": {"hi": {"ij": "value5", "jk": "value6", "kl": "value7"}, "gh": "value4"}, "op": {"pq": "value13", "qs": "value15", "qr": "value14"}}, "bc": "value2", "cd": "value1"}}

预期输出是。
 {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}

请修复我的代码。
3个回答

4
非常简单的解决方案,它在递归步骤非常早的时候执行,并且具有非常简单的逻辑。
def translateKeys (obj, keyNames):
    if isinstance(obj, dict):
        return {keyNames.get(k, k): translateKeys(v, keyNames) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [translateKeys(v, keyNames) for v in obj]
    else:
        return obj

相比于期望一个特定类型的输入,该方法接受任何形式的输入(字典、列表或其他类型),并处理其项,对于每个值调用自身。这避免了在循环中迭代 obj 本身并检查每个项目的价值的情况。

在您的示例数据上使用:

>>> dict1 = {"key1":{"key3":"value1","key2":"value2","key4":{"key5":"value3","key6":{"key7":"value4","key8":{"key9":"value5","key10":"value6","key55":"value7"}},"key11":{"key12":"value8","key13":"value9"},"key14":[{"key15":"value10","key16":"value11","key17":"value12"},{"key15":"value13","key16":"value14","key17":"value15"}]}}}
>>> dict2 = {"key1":"ab","key2":"bc","key3":"cd","key4":"de","key5":"ef","key6":"fg","key7":"gh","key8":"hi","key9":"ij","key10":"jk","key55":"kl","key11":"lm","key12":"mn","key13":"no","key14":"op","key15":"pq","key16":"qr","key17":"qs"}
>>> expected =  {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}
>>> result = translateKeys(dict1, dict2)
>>> result
{'ab': {'de': {'fg': {'gh': 'value4', 'hi': {'ij': 'value5', 'jk': 'value6', 'kl': 'value7'}}, 'op': [{'qr': 'value11', 'pq': 'value10', 'qs': 'value12'}, {'qr': 'value14', 'pq': 'value13', 'qs': 'value15'}], 'ef': 'value3', 'lm': {'no': 'value9', 'mn': 'value8'}}, 'cd': 'value1', 'bc': 'value2'}}
>>> result == expected
True

如果您想反转此翻译,只需反转keyNames并对结果执行翻译即可:
>>> result = translateKeys(dict1, dict2)
>>> invertedKeyNames = {v: k for k, v in dict2.items()}
>>> original = translateKeys(result, invertedKeyNames)
>>> original == dict1
True

它是否通用?这意味着它能够处理任何JSON吗?例如,键的值可以是简单值、列表、字典或其他嵌套的有效组合,根据JSON结构而定。 - Ritzor
是的,它应该能够处理 obj 作为任何类型的反序列化 JSON 对象的情况。由于JSON对象只能是对象(=字典)、数组(=列表)或值(数字、字符串、truefalsenull),并且我们处理对象和数组的递归(因为这些是唯一可能包含其他需要键翻译的JSON对象的类型),所以它应该能够处理来自JSON的任何有效对象。 - poke
你能否提供另一个函数,通过使用dict2和result来检索dict1? - Ritzor
当然,你只需要反转“dict2”字典,这样映射就是另一个方向,然后将其应用于之前的结果。请看我的编辑答案。 - poke

1
我认为这可以仅使用一个函数解决这个谜题。
def walk(dict1, dict2):
    res = dict()
    for k,v in dict1.items():
        if isinstance(v,list):
            newv = [walk(x, dict2) for x in v]
        elif isinstance(v,dict):
            newv = walk(v, dict2)
        else:
            newv = v
        res[dict2.get(k, k)] = newv # keep the same key if not present in dict2
    return res

expected = {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}

output = walk(dict1, dict2)
print(output)
print(output == expected)

因为它会生成

{'ab': {'de': {'lm': {'no': 'value9', 'mn': 'value8'}, 'ef': 'value3', 'fg': {'hi': {'ij': 'value5', 'kl': 'value7', 'jk': 'value6'}, 'gh': 'value4'}, 'op': [{'qr': 'value11', 'qs': 'value12', 'pq': 'value10'}, {'qr': 'value14', 'qs': 'value15', 'pq': 'value13'}]}, 'cd': 'value1', 'bc': 'value2'}}
True

基本上,它检查字典中的每个值:

  • 如果它是一个值列表,则将其应用于列表中的每个项
  • 如果它是一个字典,则对其进行应用
  • 如果它是一个文字值,则使用它

编辑:

如果输入的字典实际上不仅仅是字典,而是可以成为更通用的任何可接受的json元素(例如列表、字典、值)

def walk(obj, keys):
    if isinstance(obj,list):
        return [walk(x, keys) for x in obj]
    elif isinstance(obj,dict):
        return {keys.get(k, k): walk(v, keys) for k,v in obj.items()}
    else:
        return obj

这正是@Poke一开始回答的内容,向他致敬。

编辑2:

如果您想要恢复到原始字典dict1,只要提供的值都是不交集的(即dict2映射是双射函数),您可以执行以下操作:

back2dict1 = walk(output, {v:k for k,v in dict2.items()})
print(back2dict1)
print(back2dict1 == dict1)

which produces

{'key1': {'key3': 'value1', 'key2': 'value2', 'key4': {'key5': 'value3', 'key11': {'key12': 'value8', 'key13': 'value9'}, 'key14': [{'key15': 'value10', 'key16': 'value11', 'key17': 'value12'}, {'key15': 'value13', 'key16': 'value14', 'key17': 'value15'}], 'key6': {'key7': 'value4', 'key8': {'key10': 'value6', 'key55': 'value7', 'key9': 'value5'}}}}}
True

res[dict2[k]] = newv 应该改为 res[dict2.get(k, k)] = newv,以处理 k 不在 dict2 中的情况。 - Dan D.
@DanD。是的,谢谢你指出这一点。我曾经考虑过,但最终没有写下来。 - Pynchia
它是否通用?这意味着它能够处理任何JSON吗?例如,键的值可以是简单值、列表、字典或其他嵌套的有效组合,根据JSON结构而定。 - Ritzor
顺便提一下,您可能希望更新问题的标题和描述,以更准确地反映您的要求。这将有助于未来遇到相同问题的读者。 - Pynchia
顺便说一句,感谢您的意见,您能否提供另一个函数来通过dict2和result检索dict1? - Ritzor

1
我认为这与您的 walk_list 函数有关,输出变量被赋值而不是追加。这是我的版本:
dict1 = {"key1":{"key3":"value1","key2":"value2","key4":{"key5":"value3","key6":{"key7":"value4","key8":{"key9":"value5","key10":"value6","key55":"value7"}},"key11":{"key12":"value8","key13":"value9"},"key14":[{"key15":"value10","key16":"value11","key17":"value12"},{"key15":"value13","key16":"value14","key17":"value15"}]}}}
dict2 = {"key1":"ab","key2":"bc","key3":"cd","key4":"de","key5":"ef","key6":"fg","key7":"gh","key8":"hi","key9":"ij","key10":"jk","key55":"kl","key11":"lm","key12":"mn","key13":"no","key14":"op","key15":"pq","key16":"qr","key17":"qs"}

def walk(dict1, dict2):
    output = {}
    for key, value in dict1.iteritems():

        if isinstance(value, dict):
            outVal = walk(value, dict2)
        elif isinstance(value, list):
            outVal = walk_list(value, dict2)
        else:
            outVal = value
        output[dict2[key]] = outVal

    return output

def walk_list(sublist, dict2):
    output = []
    for i in sublist:
        if isinstance(i, dict):
            outVal = walk(i, dict2)
        elif isinstance(i, list):
            outVal = walk_list(i, dict2)
        else:
            outVal = i
        output.append(outVal)

    return output            

mine = walk(dict1, dict2)
expecting = {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}

print mine == expecting

它是否通用?这意味着它能够处理任何JSON吗?例如,键的值可以是简单值、列表、字典或其他嵌套的有效组合,根据JSON结构而定。 - Ritzor
是的,我相信。 :) - TimSC
顺便说一句,感谢您的输入,您能否提供另一个函数来通过 dict2 和 result 检索 dict1? - Ritzor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接