首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用ijson.parse()和ijson.items()加载一个大JSON文件--为什么这样做?

使用ijson.parse()和ijson.items()加载一个大JSON文件--为什么这样做?
EN

Stack Overflow用户
提问于 2020-10-08 20:34:07
回答 2查看 7.1K关注 0票数 0

我正在尝试加载对于json.load来说太大的JSON文件。我花了一段时间研究ijson和许多堆栈溢出帖子,并使用了以下代码,大部分代码是从https://stackoverflow.com/a/58148422/11357695窃取的:

代码语言:javascript
运行
复制
def extract_json(filename):
    listJ=[]
    with open(filename, 'rb') as input_file:
        jsonobj = ijson.items(input_file, 'records.item', use_float=True)
        jsons = (o for o in jsonobj)
        for j in jsons:
            listJ.append(j)
    return listJ

我的JSON文件是作为一个数据集读取的,其中有6个键,其中一个是'records'。上面的函数只复制这个'records'键的值的内容。我进一步研究了这一点,得出了ijson.items使用前缀('records.item')的结论。所以这并不奇怪,它只是在复制这把钥匙的价值。但我想要所有的东西。

为了实现这一点,我研究了如何使用ijson.parse提供一个前缀列表。当我使用迭代循环将下面的怪异生成器parser对象生成的所有前缀输入ijson.items()时,我很快就从json.items()语句中得到了一个MemoryError。我还在代码的早期迭代中获得了IncompleteJSONError,它不与当前版本一起出现。但是,如果删除except ijson.IncompleteJSONError语句,则会得到一个Memory Error

代码语言:javascript
运行
复制
def loadBigJsonBAD(filename):
    with open(filename, 'rb') as input_file:
        parser = ijson.parse(input_file)
        prefixes=[]
        for prefix , event, value in parser:
            prefixes.append(prefix)
    listJnew=[]
    with open(filename, 'rb') as input_file:
        for prefix in prefixes:
            jsonobjn = ijson.items(input_file, prefix, use_float=True)
            try:
                jsonsn = (o for o in jsonobjn)
                for jn in jsonsn:
                    listJnew.append(jn)
            except ijson.IncompleteJSONError:
                continue
    return listJnew

我尝试了如果我只是搜索没有'record'的前缀会发生什么,看看这是否至少会给我字典的其余部分。但是,它实际上工作得很好,并生成了一个列表,其第一个对象与为json.load生成的对象相同(在本例中,该对象在我使用一个小文件测试代码时起作用):

代码语言:javascript
运行
复制
def loadBigJson(filename):
    with open(filename, 'rb') as input_file:
        parser = ijson.parse(input_file)
        prefixes=[]
        for prefix , event, value in parser:
            if prefix[0:len('records')] != 'records':
                prefixes.append(prefix)
    listJnew=[]
    with open(filename, 'rb') as input_file:
        for prefix in prefixes:
            jsonobjn = ijson.items(input_file, prefix, use_float=True)
            try:
                jsonsn = (o for o in jsonobjn)
                for jn in jsonsn:
                    listJnew.append(jn)
            except ijson.IncompleteJSONError:
                continue
    return listJnew

在测试这一点时:

代码语言:javascript
运行
复制
path_json=r'C:\Users\u03132tk\.spyder-py3\antismashDB\GCF_010669165.1\GCF_010669165.1.json'

extractedJson=extract_json(path_json) #extracts the 'records' key value

loadedJson=json.load(open(path_json, 'r'))  #extracts entire json file
loadedJsonExtracted=loadedJson['records']   #the thing i am using to compare to the extractedJson item

bigJson=loadBigJson(path_json)  #a list whose single object is the same as loaded json. 

print (bigJson[0]==loadedJson)#True
print (bigJson[0]['records']==loadedJsonExtracted)#True
print (bigJson[0]['records']==extractedJson)#True

这很好,但它强调了我并不真正理解发生了什么--为什么records前缀对extract_json函数是必要的(我尝试了json字典中的其他键,没有点击),但对loadBigJson却适得其反?是什么生成错误语句,为什么except IncompleteJSONError语句阻止MemoryError

如您所知,我对使用JSON非常陌生,所以任何一般性的提示/澄清都是很棒的。

谢谢你阅读这本小说,即使你没有答案!

时间

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-10-09 08:15:41

这里有几个问题,所以我试着把它们分解一下。

为什么records前缀对extract_json函数是必需的.?

ijson需要知道什么时候应该开始构建对象。请记住,您为ijson提供了一个数据流,因此在任何时候它都不知道文档的完整结构。这意味着如果没有这个暗示,ijson就不可能猜出你的意图,或者自己想出一个。

说你有

代码语言:javascript
运行
复制
{
  "a": [1, 2, 3],
  "b": ["A", "B", "C"],
  "c": [{"i": 10, "j": 20, "k": 30},
        {"i": 11, "j": 21, "k": 31},
        {"i": 12, "j": 22, "k": 32}]
}

如果您把这个交给ijson.items,它应该产生哪些对象?如果是:

  • 123,或
  • ABC,或
  • {"i": 10, "j": 20, "k": 30}{"i": 11, "j": 21, "k": 31}{"i": 12, "j": 22, "k": 32},或
  • 102030112131122232,或
  • 123["A", "B", "C"],或[{"i": 10, "j": 20, "k": 30}, ...],或
  • 完整的物体,或者..。

哪些对象是由items构建的,这取决于您给它的前缀。如果您要给ijson提供一个records.item前缀,那么它意味着您有一个类似于以下内容的records.item文档:

代码语言:javascript
运行
复制
{
  ...
  "records": [.....],
  ...
}

并且您希望将该列表的值作为单个对象返回。

如果我正确地理解了您问题的行间,我认为您所遇到的根本问题是ijson.items只对一个前缀进行操作,但是您希望从不同的前缀中提取对象。这个功能还没有在ijson中出现,但实际上可以添加(我认为应该不会太困难)。类似的想法是支持“通配符”(例如,类似于*.items的前缀),我认为也可以支持它。

尽管如此,请看一下kvitems函数。它为给定的前缀返回key, value对,这听起来或多或少像您所需要的。

(我试过json字典中的其他键,没有点击)

如果您可以共享JSON文件的摘录或简化示例,则可以对其进行注释。

..。但对loadBigJson却适得其反?

因为loadBigJsonBADloadBigJson都有缺陷。

首先,它们都使用未重置的文件多次调用ijson.parse。第一次调用将工作,但将耗尽文件对象(即,read将不返回任何内容。进一步的调用使用这个耗尽的文件对象并失败,因为没有什么可读取的,因此它们会引发IncompleteJSONError。

其次,ijson.parse为JSON文档中的每个事件生成一个prefix,key,value元组:当对象启动、对象结束、数组启动和结束时,以及当找到原子值(字符串、数字、bools)时。将所有这些前缀累加到一个列表中会给您带来比您所需要的更多的条目,而且其中许多项将被重复。你至少应该把它们放在一个集合里,否则你就是在重复自己。

是什么产生了错误语句,为什么除了IncompleteJSONError语句会阻止MemoryError?

你在哪里得到一个MemoryError,在多长时间之后?我所能想到的唯一可能性是,您使用的是3.0.0 <= ijson < 3.1.2和yajl2_c后端,并且您正在通过创建太多的ijson.items对象来泄漏内存(参见这个错误报告)。但是,如果首先使用set来存储前缀,那么这种情况可能不会发生。

谢谢你读这本小说

不客气!

另外,请注意,与其循环遍历items的结果并将值附加到列表中(也就是说,如果您真的想一次收集内存中的所有对象),您应该能够直接从迭代器构建一个列表。因此,与其:

代码语言:javascript
运行
复制
def extract_json(filename):
    listJ=[]
    with open(filename, 'rb') as input_file:
        jsonobj = ijson.items(input_file, 'records.item', use_float=True)
        jsons = (o for o in jsonobj)
        for j in jsons:
            listJ.append(j)
    return listJ

你应该能够:

代码语言:javascript
运行
复制
def extract_json(filename):
    with open(filename, 'rb') as input_file:
        return list(ijson.items(input_file, 'records.item', use_float=True))

编辑1:

对于给定JSON示例的结构,您可能希望使用空前缀使用kvitems。所以:

代码语言:javascript
运行
复制
for key, value in ijson.kvitems(input_file, ''):
    # (key, value) will be:
    #  (key1, ”string”),
    #  ("records", [list with lots of nested dictionary/list objects])
    #  ("key3", int)
    #  ("key4", string)
    #  ("key5", {dict with nested dictionary objects})
    #  ("key6", str)

这听起来和你想要达到的目标完全一样,并且已经为你做了所有的事情。每次迭代都会给出一个不同的(key, value)对,这将迭代地完成,只需要使用足够多的内存来保存这个特定的对。如果您仍然希望将所有内容都放在一个列表中,您可以这样做,但是请注意,对于大文件,您可能会耗尽内存(这就是使用ijson v/s json的目的,对吗?)

所以是的,您的新代码是通用的,但是: 1)如果您使用kvitems,它可以变得更简单;2)它在某种程度上违背了遍历大文件的目的,因为您仍然在内存中积累所有内容。

关于“无效字符”错误,是的,这可能是一个问题。我在这里只是猜测一下,但是如果您将JSON内容从JSON生成器链接复制/粘贴到编辑器中并保存该文件,很可能它是用本地默认编码而不是UTF8编码的,这就是产生错误的原因。我尝试使用"Download文件“选项,效果很好。

票数 1
EN

Stack Overflow用户

发布于 2020-10-09 20:21:23

(注

啊,谢谢你这样做,尤其是ijson所做的具体工作--我已经把头撞在上面好一阵子了!您在extract_json上的评论是正确的,阅读起来要好得多。Wrt JSON结构,我看不到附加文件的选项,但它被格式化为:

代码语言:javascript
运行
复制
{
key 1: ”string”,
“records”: [list with lots of nested dictionary/list objects]
key3: int
key4: string
key5: {dict with nested dictionary objects}
key6: str
}

‘records’拥有我想要的大部分信息,但是我把它当作一个学习练习,所以我想得到所有的东西。您对其他两个函数的问题也是正确的,给了我修改python中文件工作方式的机会!是的,有许多(许多)前缀,甚至set()为ijson.items做了一个很长的列表。我仔细查看了前缀,并决定将它们减少到那些具有<1 ‘.’的前缀,即如果我理解正确的话,即初始键。当它被合并到下面的代码中时,它工作正常,没有任何错误。

代码语言:javascript
运行
复制
def NewJsonLoad(filename):
    with open(filename, 'rb') as input_file:
        #get all prefixes in json file
        prefixes=[]
        parser = ijson.parse(input_file)
        for prefix , event, value in parser:
                prefixes.append(prefix)
        prefixes = list(set(prefixes))
        prefixes_filtered=[]
        
        #pull out prefixes that are the initial keys only
        for prefix in prefixes:
            if prefix.count('.')==0:
                prefixes_filtered.append(prefix)
        
        #pull out items for the filtered prefixes
        finalout=[]
        for prefix in prefixes_filtered:
            input_file.seek(0)#reset pointer - see https://stackoverflow.com/a/22590262/11357695
            jsonobjn = ijson.items(input_file, prefix, use_float=True)
            jsonsn = (o for o in jsonobjn)
            for jn in jsonsn:
                finalout.append(jn)
    return finalout[0]#feeding jn into a list object, not the original dict object - this is item [0]

你认为这是可行的,可能是泛泛而谈,还是我遗漏了一些更明显的错误?

我试图找到一些虚拟的csv来玩(https://www.json-generator.com/),以测试它是否在生成我正在使用的JSON文件的程序没有格式化的文件上工作。然而,由于某些原因,我的函数和json.load都不喜欢它--可能与解码有关,我已经看到这个术语被抛来抛去:P

代码语言:javascript
运行
复制
IncompleteJSONError: lexical error: invalid char in json text.
                                       [    {      "_id": "5f80c3b4
                     (right here) ------^ 

为帮助/教程干杯!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64270230

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档