我正在尝试处理稍大(可能高达200M)的JSON文件。 该文件的结构基本上是一个对象数组。
这就是:
[
{"property":"value", "property2":"value2"},
{"prop":"val"},
...
{"foo":"bar"}
]
每个对象都有任意属性,并且不需要与数组中的其他对象共享它们(例如,具有相同的对象)。
我想对数组中的每个对象应用处理,因为文件可能很大,我不能在内存中篡改整个文件内容,解码JSON并迭代PHP数组。
理想情况下,我想阅读该文件,获取每个对象的足够信息并进行处理。 如果有类似的库可用于JSON,那么SAX类型的方法就可以了。
关于如何最好地处理这个问题的任何建议?
答案 0 :(得分:15)
我为PHP 7编写了一个流式JSON拉解析器pcrov/JsonReader,其中包含基于XMLReader的api。
它与基于事件的解析器的不同之处在于,不是设置回调并让解析器执行其操作,而是调用解析器上的方法来移动或根据需要检索数据。找到你想要的位,想要停止解析?然后停止解析(并调用close()
因为这是件好事。)
(有关拉动与基于事件的解析器的略长概述,请参阅XML reader models: SAX versus XML pull parser。)
从JSON中读取整个对象。
use pcrov\JsonReader\JsonReader;
$reader = new JsonReader();
$reader->open("data.json");
$reader->read(); // Outer array.
$depth = $reader->depth(); // Check in a moment to break when the array is done.
$reader->read(); // Step to the first object.
do {
print_r($reader->value()); // Do your thing.
} while ($reader->next() && $reader->depth() > $depth); // Read each sibling.
$reader->close();
Array
(
[property] => value
[property2] => value2
)
Array
(
[prop] => val
)
Array
(
[foo] => bar
)
对象作为字符串键控数组返回(部分)归因于有效JSON将产生PHP对象中不允许的属性名称的边缘情况。解决这些冲突是不值得的,因为贫穷的stdClass对象无论如何都不会对简单数组产生任何价值。
分别阅读每个命名元素。
$reader = new pcrov\JsonReader\JsonReader();
$reader->open("data.json");
while ($reader->read()) {
$name = $reader->name();
if ($name !== null) {
echo "$name: {$reader->value()}\n";
}
}
$reader->close();
property: value
property2: value2
prop: val
foo: bar
读取给定名称的每个属性。额外奖励:从字符串而不是URI读取,加上从同一对象中具有重复名称的属性获取数据(在JSON中允许,有多么有趣。)
$json = <<<'JSON'
[
{"property":"value", "property2":"value2"},
{"foo":"foo", "foo":"bar"},
{"prop":"val"},
{"foo":"baz"},
{"foo":"quux"}
]
JSON;
$reader = new pcrov\JsonReader\JsonReader();
$reader->json($json);
while ($reader->read("foo")) {
echo "{$reader->name()}: {$reader->value()}\n";
}
$reader->close();
foo: foo
foo: bar
foo: baz
foo: quux
如何最好地通读您的JSON取决于它的结构以及您想要用它做什么。这些例子应该为你提供一个起点。
答案 1 :(得分:14)
我决定开发一个基于事件的解析器。它还没有完成,当我推出一个令人满意的版本时,它会用我工作的链接编辑问题。
修改强>
我终于找到了一个我满意的解析器版本。它可以在GitHub上找到:
https://github.com/kuma-giyomu/JSONParser
可能还有一些改进空间,欢迎提供反馈。
答案 2 :(得分:2)
存在类似的内容,但仅适用于C++和Java。除非您可以从PHP访问其中一个库,否则在PHP中没有这个实现,但据我所知json_read()
。}
。但是,如果json的结构很简单,那么只需读取文件直到下一个json_read()
,然后处理通过{{1}}收到的JSON。但你应该更好地做那个缓冲,比如读10kb,拆分},如果找不到,再读10k,然后处理找到的值。然后阅读下一个块等等..
答案 3 :(得分:2)
这是一个简单的流式解析器,用于处理大型JSON文档。使用它来解析非常大的JSON文档,以避免将整个内容加载到内存中,这就是PHP的每个其他JSON解析器的工作方式。
答案 4 :(得分:0)
有http://github.com/sfalvo/php-yajl/我自己没有使用它。
答案 5 :(得分:0)
最近,我制作了一个名为JSON Machine的库,该库可以有效地解析无法预测的大JSON文件。通过简单的foreach
使用。我自己在项目中使用它。
示例:
foreach (JsonMachine::fromFile('employees.json') as $employee) {
$employee['name']; // etc
}
答案 6 :(得分:0)
我知道JSON流解析器https://github.com/salsify/jsonstreamingparser已经被提及。但是,正如我最近在(ish)上添加一个新的侦听器,尝试使其更易于使用,我想我可以(进行更改)提供一些有关其功能的信息...
在https://www.salsify.com/blog/engineering/json-streaming-parser-for-php上有一篇关于基本解析器的很好的文章,但是我在标准设置中遇到的问题是,您总是必须编写一个侦听器来处理文件。这并非总是一件容易的事,如果/当JSON更改时,也可能需要一定数量的维护。所以我写了RegexListener
。
基本原理是允许您说出您感兴趣的元素(通过正则表达式),并给其回调以说出找到数据时的操作。在读取JSON的同时,它会跟踪每个组件的路径-与目录结构类似。因此,/name/forename
或对于数组/items/item/2/partid
而言,这就是正则表达式所针对的。
一个例子是(来自source on github)...
$filename = __DIR__.'/../tests/data/example.json';
$listener = new RegexListener([
'/1/name' => function ($data): void {
echo PHP_EOL."Extract the second 'name' element...".PHP_EOL;
echo '/1/name='.print_r($data, true).PHP_EOL;
},
'(/\d*)' => function ($data, $path): void {
echo PHP_EOL."Extract each base element and print 'name'...".PHP_EOL;
echo $path.'='.$data['name'].PHP_EOL;
},
'(/.*/nested array)' => function ($data, $path): void {
echo PHP_EOL."Extract 'nested array' element...".PHP_EOL;
echo $path.'='.print_r($data, true).PHP_EOL;
},
]);
$parser = new Parser(fopen($filename, 'r'), $listener);
$parser->parse();
仅几个解释...
'/1/name' => function ($data)
因此/1
是数组(基于0)中的第二个元素,因此可以访问元素的特定实例。 /name
是name
元素。然后将值作为$data
"(/\d*)" => function ($data, $path )
这将选择数组的每个元素并一次传递一个,因为它正在使用捕获组,因此此信息将作为$path
传递。这意味着当文件中存在一组记录时,您可以一次处理一项。并且无需跟踪即可知道哪个元素。
最后一个
'(/.*/nested array)' => function ($data, $path):
有效地扫描任何称为nested array
的元素,并将每个元素连同其在文档中的位置一起传递。
我发现的另一个有用的功能是,如果在一个大型JSON文件中,您只想将摘要详细信息放在顶部,则可以抓住这些位然后停下来...
$filename = __DIR__.'/../tests/data/ratherBig.json';
$listener = new RegexListener();
$parser = new Parser(fopen($filename, 'rb'), $listener);
$listener->setMatch(["/total_rows" => function ($data ) use ($parser) {
echo "/total_rows=".$data.PHP_EOL;
$parser->stop();
}]);
当您对其余内容不感兴趣时,这可以节省时间。
要注意的一件事是,这些内容将对内容做出反应,以便在找到匹配内容的结尾时触发每个内容,并且它们的顺序可能不同。而且解析器只会跟踪您感兴趣的内容,而丢弃其他任何内容。
如果您发现任何有趣的功能(有时可怕地称为bug),请在github页面上让我知道或报告问题。