2015-06-26 111 views
0

我正在处理一个需要处理非常大的JSON文件的程序,所以我想使用面向流事件的阅读器(如jsonstreamingparser),以便我们可以避免加载整个结构一次进入内存。我关心的事情是似乎是做这项工作所需的对象结构。 例如,假设我写像Evite一个程序来邀请函发送到活动中,有一个JSON结构,如:如何在处理JSON流中处理嵌套对象

{ 
    "title": "U2 Concert", 
    "location": "San Jose", 
    "attendees": [ 
    {"email": "[email protected]"}, 
    {"email": "[email protected]"} 
    ], 
    "date": "July 4, 2015" 
} 

我希望做的是有一个程序的“事件”,当流遇到新的与会者,发出邀请电子邮件。 ,我不能这样做,因为流尚未达到事件的日期。
当然,考虑到这个例子,将所有内容都读入内存很好 - 但是我的数据集包含复杂的对象,“参与者”属性就是这样,而且可能有成千上万个属性。

另一个“解决方案”只是强制要求:您必须先将所有必需的“父”属性放在第一位,但这正是我试图寻找解决办法的方法。

任何想法?

+0

的解决方案将读取数据流的两倍。您第一次阅读所有强制性属性。第二次处理与会者并发出邀请(这不是很优雅,但只有流,并且您控制了内存限制) – Mat

+0

嗯,我对JSON结构有一些控制权。我担心的是,生成JSON的客户端可能不会像对象属性的排序那样控制事物......所以当他们告诉他们的代码将对象转换为JSON时,对象属性顺序可能不可控。 –

+0

iam_decoder - 感谢您的编辑! –

回答

0

这是另一个'走树'问题。 JSON streaming parser读取源文件并开始构建'树'。它通过收集“元素”并将它们存储在内存中来实现这一点。为了使我们能够处理每个条目,它在方便的时候“发布事件”。这意味着它会根据需要调用你的函数传递有用的值。 '树事件' 的

的例子是:

  • start_object()
  • end_object()
  • start_array()
  • end_array()
  • ...

    'Parser'提供的'示例'代码是一个使用解析器在内存中构建树的程序。我只是修改了这个例子来调用我们的函数,只要它有一个'完整的事件'存储。

那么,我们如何确定一个'完整的事件'?

输入文件由一个数组组成,其中每个条目是JSON的“obbject”。每个对象由组成'对象'数据的'子条目'组成。

现在,当我们遍历'树'构建它时,我们的代码将在如上所示的各个点处被调用。特别是当对象和数组的“开始”和“结束”时。我们需要收集“外部对象”的所有数据。

我们如何识别?随着处理过程的进行,我们记录下“树”中的位置。我们通过跟踪树中“嵌套”的深度来做到这一点。因此,'水平'。一个对象的'开始''嵌套'一个层次,一个对象的'结束''突破'一个层次。

我们感兴趣的对象是'1级'。

提供的代码: 1)跟踪'等级',并在达到'等级1'的对象末尾时调用我们的函数。 2)从'level 1'处的对象的开始累积适当结构中的数据。

要求:

1)称之为“可赎回”程序时,有一个“完整的事件”,可以进行处理。

假设:

  • 该输入文件包含 '事件' 的阵列。

处理:

  • 解析文件
  • 每当当前事件是 '完整'
  • 执行 '的processEvent' 调用与访问当前事件。

源代码:

代码:index.php文件

<?php // https://stackoverflow.com/questions/31079129/how-to-handle-nested-objects-in-processing-a-json-stream 

require_once __DIR__ .'/vendor/jsonstreamingparser/src/JsonStreamingParser/Parser.php'; 
require_once __DIR__ .'/vendor/jsonstreamingparser/src/JsonStreamingParser/Listener/IdleListener.php'; 

require_once __DIR__ .'/Q31079129Listener.php'; 

/** 
* The input file consists of a JSON array of 'Events'. 
* 
* The important point is that when the file is being 'parsed' the 'listener' is 
* 'walking' the tree. 
* 
* Therefore 
* 1) Each 'Event' is at 'level 1' in the tree. 
* 
* Event Level Changes: 
* Start: level will go from 1 => 2 
* End: level will go from 2 => 1 !!!! 
* 
* Actions: 
*  The 'processEvent' function will be called when the 
*  'Event Level' changes to 2 from 1. 
* 
*/ 
define('JSON_FILE', __DIR__. '/Q31079129.json'); 

/** 
* This is called when one 'Event' is complete 
* 
* @param type $listener 
*/ 
function processEvent($listener) { 
    echo '<pre>', '+++++++++++++++'; 
    print_r($listener->get_event()); 
    echo '</pre>'; 
} 

// ---------------------------------------------------------------------- 
// the 'Listener' 
$listener = new Q31079129Listener(); 

// setup the 'Event' Listener that will be called with each complete 'Event' 
$listener->whenLevelAction = 'processEvent'; 

// process the input stream 
$stream = fopen(JSON_FILE, 'r'); 
try { 
    $parser = new JsonStreamingParser_Parser($stream, $listener); 
    $parser->parse(); 
} 
    catch (Exception $e) { 
     fclose($stream); 
     throw $e; 
} 
fclose($stream); 
exit; 

代码:Q31079129Listener.php

<?php // // https://stackoverflow.com/questions/31079129/how-to-handle-nested-objects-in-processing-a-json-stream 

/** 
* This is the supplied example modified: 
* 
* 1) Record the current 'depth' of 'nesting' in the current object being parsed. 
*/ 
class Q31079129Listener extends JsonStreamingParser\Listener\IdleListener { 

    public $whenLevelAction = null; 

    protected $event; 
    protected $prevLevel; 
    protected $level; 


    private $_stack; 
    private $_keys; 

    public function get_event() { 
     return $this->event; 
    } 

    public function get_prevLevel() { 
     return $this->prevLevel; 
    } 

    public function get_level() { 
     return $this->prevLevel; 
    } 

    public function start_document() { 
     $this->prevLevel = 0; 
     $this->level = 0; 


     $this->_stack = array(); 
     $this->_keys = array(); 
     // echo '<br />start of document'; 
    } 

    public function end_document() { 
     // echo '<br />end of document'; 
    } 

    public function start_object() { 
     $this->prevLevel = $this->level; 
     $this->level++; 
     $this->_start_complex_value('object'); 
    } 

    public function end_object() { 
     $this->prevLevel = $this->level; 
     $this->level--; 
     $this->_end_complex_value(); 
    } 

    public function start_array() { 
     $this->prevLevel = $this->level; 
     $this->level++; 
     $this->_start_complex_value('array'); 
    } 

    public function end_array() { 
     $this->prevLevel = $this->level; 
     $this->level--; 
     $this->_end_complex_value(); 
    } 

    public function key($key) { 
     $this->_keys[] = $key; 
    } 

    public function value($value) { 
     $this->_insert_value($value); 
    } 

    private function _start_complex_value($type) { 
     // We keep a stack of complex values (i.e. arrays and objects) as we build them, 
     // tagged with the type that they are so we know how to add new values. 
     $current_item = array('type' => $type, 'value' => array()); 
     $this->_stack[] = $current_item; 
    } 


    private function _end_complex_value() { 
     $obj = array_pop($this->_stack); 

    // If the value stack is now at level 1 from level 2, 
    // we're done parsing the current complete event, so we can 
    // move the result into place so that get_event() can return it. Otherwise, we 
    // associate the value 

// var_dump(__FILE__.__LINE__, $this->prevLevel, $this->level, $obj); 

    if ($this->prevLevel == 2 && $this->level == 1) { 
     if (!is_null($this->whenLevelAction)) { 
      $this->event = $obj['value']; 
      call_user_func($this->whenLevelAction, $this); 
      $this->event = null; 
     } 
    } 
    else { 
     $this->_insert_value($obj['value']); 
    } 
    } 

    // Inserts the given value into the top value on the stack in the appropriate way, 
    // based on whether that value is an array or an object. 
    private function _insert_value($value) { 
    // Grab the top item from the stack that we're currently parsing. 
    $current_item = array_pop($this->_stack); 

    // Examine the current item, and then: 
    // - if it's an object, associate the newly-parsed value with the most recent key 
    // - if it's an array, push the newly-parsed value to the array 
    if ($current_item['type'] === 'object') { 
     $current_item['value'][array_pop($this->_keys)] = $value; 
    } else { 
     $current_item['value'][] = $value; 
    } 

    // Replace the current item on the stack. 
    $this->_stack[] = $current_item; 
    } 
} 
+0

大大的微笑......我到达了类似的策略: 私人功能getLast($ ARR){ $ last = end($ ARR); reset($ arr); return $ last; } private function out($ str){echo $ str。 “\ n”;} private function getAddress(){return implode('|',$ this - > _ keys);} public function end_object(){012- {“this-> getAddress()=='devices |事件'){event = $ this-> getLast($ this - > _ stack); ('an event:'。$ event ['value'] ['orderTimestamp']);} $ this-> } $ this - > _ end_complex_value(); } –

+0

Oy,我想把代码放在注释中并不好用:( –

+0

我很欣赏你对这个问题的深入研究,但是我认为你错过了我所问的核心问题:有什么可以保护的在父类的所有属性都知道之前处理子节点。具体来说,如果处理我的示例中的事件,如果它们依赖于事件之后声明的父节点的“日期”属性。 –