Python 基于密钥组合JSON值

Python 基于密钥组合JSON值,python,json,Python,Json,我正在研究Python2.6.6,我正在努力解决一个问题 我有一个具有以下结构的大型JSON文件: {"id":"12345","ua":[{"n":"GROUP_A","v":["true"]}]} {"id":"12345","ua":[{"n":"GROUP_B","v":["true"]}]} {"id":"54321","ua":[{"n":"GROUP_C","v":["true"]}]} {"id":"54321","ua":[{"n":"GROUP_D","v":["true"]

我正在研究Python2.6.6,我正在努力解决一个问题

我有一个具有以下结构的大型JSON文件:

{"id":"12345","ua":[{"n":"GROUP_A","v":["true"]}]}
{"id":"12345","ua":[{"n":"GROUP_B","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_C","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_D","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_E","v":["true"]}]}
{"id":"98765","ua":[{"n":"GROUP_F","v":["true"]}]}
我需要合并id,以便它们包含所有组,如下所示:

{"id":"12345","ua":[{"n":"GROUP_A","v":["true"]},{"n":"GROUP_B","v":["true"]}]}
{"id":"54321","ua":[{"n":"GROUP_C","v":["true"]},{"n":"GROUP_D","v":["true"]},{"n":"GROUP_E","v":["true"]}]}
{"id":"98765","ua":[{"n":"GROUP_F","v":["true"]}]}
我尝试使用“json”库,但无法正确附加值。此外,我还尝试将所有内容转换为字典,并将值(组)作为列表附加到键(id)中,但由于需要将其打印到输出文件中,我遇到了困难

我可以使用bash来完成这项工作,但是解析所有信息并将其重新排列为所需的格式需要很长时间

感谢您的帮助


谢谢。

首先,让我们把JSON的东西放在一边

您的文件不是JSON结构,而是一组单独的JSON对象。从您的示例来看,它似乎是每行一个对象。那么,让我们把这个读入一个列表:

with open('spam.json') as f:
    things = [json.loads(line) for line in f]
然后我们会处理这个,然后写出来:

with open('eggs.json', 'w') as f:
    for thing in new_things:
        f.write(json.dumps(thing) + '\n')

现在,您没有想要附加内容的JSON结构;您有一个dict列表,您希望创建一个新的dict列表,将具有相同键的dict合并在一起

这里有一种方法:

new_things = {}
for thing in things:
    thing_id = thing['id']
    try:
        old_thing = new_things[thing_id]
    except KeyError:
        new_things[thing_id] = thing
    else:
        old_thing['ua'].extend(thing['ua'])
new_things = new_things.values()
有几种不同的方法可以简化这个过程;我就是这样写的,因为它没有使用新手无法使用的技巧。例如,您可以通过它进行排序和分组:

def merge(things):
    return {'id': things[0]['id'],
            'ua': list(itertools.chain.from_iterable(t['ua'] for t in things))}
sorted_things = sorted(things, key=operator.itemgetter('id'))
grouped_things = itertools.groupby(sorted_things, key=operator.itemgetter('id'))
new_things = [merge(list(group)) for key, group in grouped_things]

我没有从你最初的问题中意识到你有几千万行。上述所有步骤都需要将整个原始数据集加载到内存中,使用一些临时存储进行处理,然后将其写回内存。但如果您的数据集太大,您需要找到一种方法一次处理一行,同时在内存中保留尽可能少的数据


首先,要一次处理一行,只需将初始列表理解更改为生成器表达式,并将其余代码移动到
with
语句中,如下所示:

with open('spam.json') as f:
    things = (json.loads(line) for line in f)
    for thing in things:
        # blah blah
with open('spam.json') as f:
    for line in f:
        thing = json.loads(line)
        # blah blah
…在这一点上,像这样重写它可能同样容易:

with open('spam.json') as f:
    things = (json.loads(line) for line in f)
    for thing in things:
        # blah blah
with open('spam.json') as f:
    for line in f:
        thing = json.loads(line)
        # blah blah

接下来,排序显然会在内存中构建整个已排序列表,因此这在这里是不可接受的。但是如果不进行排序和分组,整个
新事物
结果对象必须同时处于活动状态(因为最后一个输入行可能必须合并到第一个输出行)


您的示例数据似乎已经按
id
对行进行了排序。如果您可以在现实生活中依靠这一点,或者仅仅依靠总是按
id
分组的行,那么只需跳过排序步骤,它除了浪费时间和内存之外什么都不做,并使用分组解决方案


另一方面,如果您不能指望按
id
对行进行分组,那么实际上只有两种方法可以进一步减少内存:以某种方式压缩数据,或者将存储返回磁盘


首先,foobar用户的解决方案构建了一个更简单、更小的数据结构(一个dict将每个id映射到它的ua列表,而不是一个dict列表,每个dict都有一个id和一个ua),它应该占用更少的内存,并且我们可以一次转换成一行的最终格式。像这样:

with open('spam.json') as f:
    new_dict = defaultdict(list)
    for row in f:
        thing = json.loads(row)
        new_dict[thing["id"]].extend(thing["ua"])
with open('eggs.json', 'w') as f:
    for id, ua in new_dict.items(): # use iteritems in Python 2.x
        thing = {'id': id, 'ua': ua}
        f.write(json.dumps(thing) + '\n')

第二,Python提供了一种很好的方法,可以像使用字典一样使用dbm数据库。如果您的值只是字符串,则可以使用
anydbm
/
dbm
模块(或特定实现之一)。由于您的值是列表,因此需要使用
shelve

无论如何,虽然这会减少内存使用,但也会减慢速度。在一台拥有4GB内存的机器上,页面文件交换的节省可能会抵消浏览数据库的额外成本……但在一台拥有16GB内存的机器上,您可能只是为了很少的收益而增加了开销。您可能想先试用较小的文件,看看
shelve
dict
相比,当内存不是问题时,代码的速度会慢多少


或者,如果事情超出了您的内存限制,您可以始终使用功能更强大的数据库,该数据库实际上可以对磁盘上的事情进行排序。例如(未经测试):


你是否设法让它作为一个字典工作,但你就是不能用正确的格式保存它?我认为这不是有效的JSON,至少根据它不是JSON结构;它是六个独立的JSON结构。它们真的在一个你没有给我们看的阵列里吗?或者你有它们的列表或其他什么吗?同时,忘记尝试“附加到JSON结构”。将JSON导入本机Python对象,将它们作为Python对象处理,然后在最后将它们写回JSON。干得好,我认为
defaultdict
会更好,但是这似乎是最好的解决办法。@FooBarUser:defaultdict的问题在于,您需要编写一个非常复杂的函数来创建所需的结构,和/或编写一个复杂的表达式来更新它。但是正如我在对你的答案的评论中所说的,如果你只构建结构
defaultdict
可以很容易地构建,我认为你可以在之后用一行代码将它转换成正确的结构。@FooBarUser:我已经编辑了你的答案来说明我的意思。我认为这是一个比我的第一个版本更好的答案,而且对新手同样友好。(我仍然喜欢我的
groupby
解决方案,但我不想尝试解释它…@abarnert-非常感谢!对于您在问题主题中的问题,它不是一个数组,而是文件的结构,它包含大约10万条这样的记录。我可以在:
上用open('eggs.json','w')作为f:for thing in new\u things:f.write(json.dumps(thing)+'\n')
运行您的代码,只需稍加调整即可。我将
new\u things
更改为
things
。我尝试在所有10M记录上运行它,但由于一些编码问题而失败,我现在尝试隔离这些问题。我能够在10M记录文件上运行它,这花费了
real 73m17.105s
,我已经看到python