2015-06-26 158 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

我很欣賞你對這個問題的深入研究,但是我認爲你錯過了我所問的核心問題:有什麼可以保護的在父類的所有屬性都知道之前處理子節點。具體來說,如果處理我的示例中的事件,如果它們依賴於事件之後聲明的父節點的「日期」屬性。 –