代码之家  ›  专栏  ›  技术社区  ›  The Mighty Rubber Duck

在PHP中处理大型JSON文件

  •  20
  • The Mighty Rubber Duck  · 技术社区  · 14 年前

    我正在尝试处理一些较大(可能高达2亿)的JSON文件。

    所以大致如下:

    [
      {"property":"value", "property2":"value2"},
      {"prop":"val"},
      ...
      {"foo":"bar"}
    ]
    

    我想对数组中的每个对象应用一个处理,由于文件可能很大,我不能在内存中slurp整个文件内容,解码JSON并在PHP数组上迭代。

    所以理想情况下,我希望读取文件,为每个对象获取足够的信息并对其进行处理。 如果JSON有类似的库,SAX类型的方法就可以了。

    对如何最好地处理这个问题有什么建议吗?

    6 回复  |  直到 14 年前
        1
  •  15
  •   user3942918    8 年前

    我已经编写了一个流式JSON pull解析器 pcrov/JsonReader 对于使用基于 XMLReader .

    它与基于事件的解析器有着显著的不同,它不是设置回调并让解析器执行它的任务,而是调用解析器上的方法来根据需要移动或检索数据。找到所需的位并要停止解析?然后停止解析(并调用 close() 因为这是件好事。)

    (有关基于pull和基于事件的解析器的较长概述,请参见 XML reader models: SAX versus XML pull parser


    例1:

    从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
    )
    

    对象作为stringly键控数组返回,部分原因是由于边缘情况,有效的JSON将生成PHP对象中不允许的属性名。解决这些冲突是不值得的,因为一个缺乏活力的stdClass对象无论如何都不会给一个简单的数组带来任何价值。


    例2:

    分别读取每个命名元素。

    $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
    

    例3:

    $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取决于它的结构和您想用它做什么。这些例子应该给你一个开始的地方。

        2
  •  14
  •   The Mighty Rubber Duck    13 年前

    我决定开发一个基于事件的解析器。它还没有完全完成,当我推出一个令人满意的版本时,它将编辑一个链接到我的工作的问题。

    编辑:

    我终于想出了一个令我满意的解析器版本。GitHub上提供:

    https://github.com/kuma-giyomu/JSONParser

    可能还有改进的余地,欢迎反馈。

        3
  •  2
  •   joni    14 年前

    有这样的东西,但只为 C++ Java . 除非您可以从PHP访问其中一个库,否则在PHP中没有实现,但是 json_read() 据我所知。但是,如果json的结构如此简单,那么在下一个 } 然后处理通过 json读取()

        4
  •  2
  •   Aaron Averill    10 年前

    这是一个简单的流式解析器,用于处理大型JSON文档。使用它来解析非常大的JSON文档,以避免将整个内容加载到内存中,这就是几乎所有其他PHP JSON解析器的工作原理。

    https://github.com/salsify/jsonstreamingparser

        5
  •  2
  •   Filip Halaxa    6 年前

    最近我制作了一个名为JSON Machine的库,它可以有效地解析不可预知的大JSON文件。使用方法很简单 foreach

    例子:

    foreach (JsonMachine::fromFile('employees.json') as $employee) {
        $employee['name']; // etc
    }
    

    https://github.com/halaxa/json-machine

        6
  •  0
  •   Alex Jasmin    14 年前

    http://github.com/sfalvo/php-yajl/ 我不是自己用的。

        7
  •  0
  •   Nigel Ren    5 年前

    我知道JSON流解析器 https://github.com/salsify/jsonstreamingparser 已经被提到了。但最近,我又增加了一个新的监听器,试图让它更易于使用,我想我会(为了改变)把一些关于它的功能的信息放出来。。。

    https://www.salsify.com/blog/engineering/json-streaming-parser-for-php ,但我在标准设置中遇到的问题是,必须始终编写侦听器来处理文件。这并不总是一个简单的任务,而且如果/当JSON发生更改时,还可能需要一定的维护。所以我写了 RegexListener .

    基本原则是允许您说出您感兴趣的元素(通过regex表达式),并给它一个回调,告诉它在找到数据时要做什么。在读取JSON时,它会跟踪到每个组件的路径——类似于目录结构。所以 /name/forename 或数组 /items/item/2/partid -这就是regex的对手。

    一个例子是 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页面上报告一个问题。