【问题标题】:Creating a tree/deeply nested dict with lists from an indented text file使用缩进文本文件中的列表创建树/深度嵌套字典
【发布时间】:2016-12-04 12:06:19
【问题描述】:

我想遍历一个文件并将每一行的内容放入一个深度嵌套的字典中,其结构由前导空格定义。这种愿望与记录在案的here 非常相似。我已经解决了这个问题,但现在遇到了处理重复键被覆盖而不是被强制转换为列表的情况的问题。

基本上:

a:
    b:      c
    d:      e
a:
    b:      c2
    d:      e2
    d:      wrench

当它应该被转换成{"a":{"b":"c2","d":"wrench"}}时被转换成

{"a":[{"b":"c","d":"e"},{"b":"c2","d":["e2","wrench"]}]}

一个独立的例子:

import json

def jsonify_indented_tree(tree):
    #convert indentet text into json
    parsedJson= {}
    parentStack = [parsedJson]
    for i, line in enumerate(tree):
        data = get_key_value(line)
        if data['key'] in parsedJson.keys(): #if parent key is repeated, then cast value as list entry
            # stuff that doesn't work
#            if isinstance(parsedJson[data['key']],list):
#                parsedJson[data['key']].append(parsedJson[data['key']])
#            else:
#                parsedJson[data['key']]=[parsedJson[data['key']]]
            print('Hey - Make a list now!')
        if data['value']: #process child by adding it to its current parent
            currentParent = parentStack[-1] #.getLastElement()
            currentParent[data['key']] = data['value']
            if i is not len(tree)-1:
                #determine when to switch to next branch
                level_dif = data['level']-get_key_value(tree[i+1])['level'] #peek next line level
                if (level_dif > 0):
                    del parentStack[-level_dif:] #reached leaf, process next branch
        else:
        #group node, push it as the new parent and keep on processing.
            currentParent = parentStack[-1] #.getLastElement()
            currentParent[data['key']] = {}
            newParent = currentParent[data['key']]
            parentStack.append(newParent)
    return parsedJson

def get_key_value(line):
    key = line.split(":")[0].strip()
    value = line.split(":")[1].strip()
    level = len(line) - len(line.lstrip())
    return {'key':key,'value':value,'level':level}

def pp_json(json_thing, sort=True, indents=4):
    if type(json_thing) is str:
        print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents))
    else:
        print(json.dumps(json_thing, sort_keys=sort, indent=indents))
    return None

#nested_string=['a:', '\tb:\t\tc', '\td:\t\te', 'a:', '\tb:\t\tc2', '\td:\t\te2']
#nested_string=['w:','\tgeneral:\t\tcase','a:','\tb:\t\tc','\td:\t\te','a:','\tb:\t\tc2','\td:\t\te2']
nested_string=['a:',
 '\tb:\t\tc',
 '\td:\t\te',
 'a:',
 '\tb:\t\tc2',
 '\td:\t\te2',
  '\td:\t\twrench']

pp_json(jsonify_indented_tree(nested_string))

【问题讨论】:

  • 您希望它们始终是一个列表,或者如果只有一个元素,则只是一个字典?我,我总是会做一个列表,只使用defaultdict
  • 输入看起来更像是一个有向多重图,而不是树,而输出是一个字典,而不是 json。数组中的最后一个元素不称为叶子。您没有指定多级缩进应该发生什么,只有 2 级应该形成字符串 dicts 列表的 dict ..您能否尝试使您的意图(业务逻辑)更清晰?
  • 输入格式在某处有名称/规格吗?
  • 好点@Aprillion。输出是一个字典,虽然我打算把它变成 json。我的例子可能太短了,但那是因为它引用了这个 post 和水果例子。如果这不能说明问题,我可以改进我正在使用的实际数据来制作一个更强大的示例
  • 看起来一个答案消失了,有一点很好,isinstance(parsedJson[data['key']],list) 应该在data['key'] in parsedJson 前面,以避免KeyError 异常...

标签: python json parsing data-structures nested


【解决方案1】:

这种方法(逻辑上)更直接(虽然更长):

  1. 跟踪多行字符串中每一行的 levelkey-value
  2. 将此数据存储在列表的level 键控字典中: {level1:[dict1,dict2]}
  3. 仅在 key-only 行中附加表示键的字符串:{level1:[dict1,dict2,"nestKeyA"]}
  4. 由于 key-only 行意味着下一行更深一层,因此在下一层处理:{level1:[dict1,dict2,"nestKeyA"] ,level2:[...]}。更深层次的level2 的内容本身可能只是另一个key-only 行(并且下一个循环将添加一个新级别level3 使其变为{level1:[@ 987654340@,dict2,"nestKeyA"],level2:["nestKeyB"],level3:[...]}) 或新的字典 dict3 这样 {level1:[@987654348 @,dict2,"nestKeyA"],level2:[dict3]
  5. 步骤 1-4 继续,直到当前行的缩进小于前一行(表示返回到某个先前的范围)。这就是我的示例中每行迭代的数据结构。

    0, {0: []}
    1, {0: [{'k': 'sds'}]}
    2, {0: [{'k': 'sds'}, 'a']}
    3, {0: [{'k': 'sds'}, 'a'], 1: [{'b': 'c'}]}
    4, {0: [{'k': 'sds'}, 'a'], 1: [{'b': 'c'}, {'d': 'e'}]}
    5, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: []}
    6, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: [{'b': 'c2'}]}
    7, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: [{'b': 'c2'}, {'d': 'e2'}]}
    

    那么有两件事需要发生。 1:需要检查 dict 列表中是否包含重复键和任何重复的 dict 值组合在一个列表中 - 这将在稍后演示。 2:从第 4 次迭代到第 5 次迭代可以看出,最深层次的 dicts 列表(此处为 1)被合并为一个 dict... 最后,为了演示重复处理,请观察:

    [7b, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: [{'b': 'c2'}, {'d': 'e2'}, {'d': 'wrench'}]}]
    [7c, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, {'a': {'d': ['wrench', 'e2'], 'b': 'c2'}}], 1: []}]
    

    wrenche2 被放置在一个列表中,该列表本身进入一个由其原始键键入的字典。

  6. 重复第 1-5 步,将更深的作用域字典向上提升到其父键上,直到达到当前行的作用域(级别)。

  7. 处理终止条件以将第 0 级的 dict 列表组合成一个 dict。

代码如下:

import json

def get_kvl(line):
    key = line.split(":")[0].strip()
    value = line.split(":")[1].strip()
    level = len(line) - len(line.lstrip())
    return {'key':key,'value':value,'level':level}

def pp_json(json_thing, sort=True, indents=4):
    if type(json_thing) is str:
        print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents))
    else:
        print(json.dumps(json_thing, sort_keys=sort, indent=indents))
    return None

def jsonify_indented_tree(tree): #convert shitty sgml header into json
    level_map= {0:[]}
    tree_length=len(tree)-1
    for i, line in enumerate(tree):
        data = get_kvl(line)
        if data['level'] not in level_map.keys():
            level_map[data['level']]=[] # initialize
        prior_level=get_kvl(tree[i-1])['level']
        level_dif = data['level']-prior_level # +: line is deeper, -: shallower, 0:same
        if data['value']:
            level_map[data['level']].append({data['key']:data['value']})
        if not data['value'] or i==tree_length:
            if i==tree_length: #end condition
                level_dif = -len(list(level_map.keys()))        
            if level_dif < 0:
                for level in reversed(range(prior_level+level_dif+1,prior_level+1)): # (end, start)
                    #check for duplicate keys in current deepest (child) sibling group,
                    # merge them into a list, put that list in a dict 
                    key_freq={} #track repeated keys
                    for n, dictionary in enumerate(level_map[level]):
                        current_key=list(dictionary.keys())[0]
                        if current_key in list(key_freq.keys()):
                            key_freq[current_key][0]+=1
                            key_freq[current_key][1].append(n)
                        else:
                            key_freq[current_key]=[1,[n]]
                    for k,v in key_freq.items():
                        if v[0]>1: #key is repeated
                            duplicates_list=[]
                            for index in reversed(v[1]): #merge value of key-repeated dicts into list
                                duplicates_list.append(list(level_map[level].pop(index).values())[0])
                            level_map[level].append({k:duplicates_list}) #push that list into a dict on the same stack it came from
                    if i==tree_length and level==0: #end condition
                        #convert list-of-dict into dict
                        parsed_nest={k:v for d in level_map[level] for k,v in d.items()}
                    else:
                        #push current deepest (child) sibling group onto parent key
                        key=level_map[level-1].pop() #string
                        #convert child list-of-dict into dict
                        level_map[level-1].append({key:{k:v for d in level_map[level] for k,v in d.items()}})
                        level_map[level]=[] #reset deeper level
            level_map[data['level']].append(data['key'])
    return parsed_nest

nested_string=['k:\t\tsds', #need a starter key,value pair otherwise this won't work... fortunately I always have one
 'a:',
 '\tb:\t\tc',
 '\td:\t\te',
 'a:',
 '\tb:\t\tc2',
 '\td:\t\te2',
 '\td:\t\twrench']

pp_json(jsonify_indented_tree(nested_string))

【讨论】:

    猜你喜欢
    • 2013-07-25
    • 2018-05-14
    • 1970-01-01
    • 2020-10-09
    • 2021-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多