根据键组合JSON值

时间:2013-10-03 00:24:30

标签: python json

我正在研究Python 2.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,因此它们将包含所有的GROUPS:

{"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'库,但我无法正确附加值。此外,我试图将它全部转换为字典,并将值(GROUPS)作为列表附加到键(id),但是因为我需要输出文件而无法打印它。

我可以使用bash来完成它,但解析所有信息并以所需格式重新排列它需要很长时间。

感谢任何帮助!

感谢。

1 个答案:

答案 0 :(得分:5)

首先,让我们把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结构;你有一个dicts列表,你想创建一个新的dicts列表,将这些dicts合并在一起。

这是一种方法:

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

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


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


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


首先,Foo Bar User的解决方案构建了一个更简单,更小的数据结构(一个dict将每个id映射到其uas列表,而不是一个dicts列表,每个都有一个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 RAM的计算机上,页面文件交换的节省可能会减少通过数据库的额外成本......但是在具有16GB RAM的计算机上,您可能只是增加开销而获得的收益非常小。您可能希望首先尝试使用较小的文件,以便在内存不存在问题时查看shelvedict代码的速度有多慢。


或者,如果事情超出了你的记忆限制,你总是可以使用一个更强大的数据库,实际上可以对磁盘上的东西进行排序。例如(未经测试):

db = sqlite3.connect('temp.sqlite')
c = db.cursor()
c.execute('CREATE TABLE Things (tid, ua)')
for thing in things:
    for ua in thing['ua']:
        c.execute('INSERT INTO Things (tid, ua) VALUES (?, ?)',
                  thing['id'], ua)
c.commit()
c.execute('SELECT tid, ua FROM Things ORDER BY tid')
rows = iter(c.fetchone, None)
grouped_things = itertools.groupby(rows, key=operator.itemgetter(0))
new_things = (merge(list(group)) for key, group in grouped_things)
with open('eggs.json', 'w') as f:
    for thing in new_things:
        f.write(json.dumps(thing) + '\n')