salsify/jsonstreamingparser

repository·master·Indexed 20 days ago

https://github.com/salsify/jsonstreamingparser

A streaming JSON parser for PHP designed to process very large JSON documents efficiently by avoiding loading the entire document into memory. It utilizes a listener pattern similar to a SAX parser, requiring the implementation of the \JsonStreamingParser\Listener interface. The library includes specialized listeners such as CorruptedJsonListener for repairing truncated documents, GeoJsonListener for processing GeoJSON features, RegexListener for path-based extraction, and SimpleObjectQueueListener for flat arrays of objects.

Tokens
4.2K
Snippets
15
Records
20
Agent score
73%

What's inside salsify-jsonstreamingparser

  1. Implement the Listener interface

    master
    To process JSON data as it is being parsed, you must create a class that implements the \JsonStreamingParser\Listener interface. The parser will trigger events on this listener as it encounters different parts of the JSON structure (e.g., start of object, key, value, etc.).
  2. How to use the streaming JSON parser

    master

    The JsonStreamingParser works by using a listener pattern similar to a SAX parser for XML. To use it, you must implement the \JsonStreamingParser\Listener interface. This interface allows your custom listener to receive events from the parser as it processes the JSON stream, enabling you to handle large documents without loading them entirely into memory.

    Once your listener is implemented, pass the file stream and your listener instance to the \JsonStreamingParser\Parser class and call the parse() method.

    $stream = fopen('doc.json', 'r');
    $listener = new YourListener(); // YourListener must implement \JsonStreamingParser\Listener
    try {
      $parser = new \JsonStreamingParser\Parser($stream, $listener);
      $parser->parse();
      fclose($stream);
    } catch (Exception $e) {
      fclose($stream);
      throw $e;
    }
  3. Use CorruptedJsonListener to repair cut JSON documents

    master

    The CorruptedJsonListener is a specialized implementation of ListenerInterface designed to construct an in-memory representation of a JSON document. It is particularly useful for repairing "cut" JSON documents—files that end unexpectedly before the structure is complete. By using this listener, you can capture the partially parsed data and then call forceEndDocument() to close all open objects and arrays, effectively reconstructing a valid JSON structure from the truncated input.

    // Conceptual usage pattern
    $listener = new \JsonStreamingParser\Listener\CorruptedJsonListener();
    $parser = new \JsonStreamingParser\Parser($listener);
    
    // If the stream ends prematurely, you can attempt to repair it:
    $listener->forceEndDocument();
    $repairedJson = $listener->getJson();
  4. Select JSON elements using RegexListener

    master

    The RegexListener allows you to extract specific elements from a JSON stream by matching their path against regular expressions. This is useful for processing large JSON files where you only need a subset of the data.

    Path Syntax

    Paths are constructed using forward slashes (/).

    • Root attributes: /name matches a name attribute at the root.
    • Array elements: /1/name matches the name attribute of the second element (index 1) in an array.
    • Wildcards: /\d* matches any element within an array.

    Capture Groups

    You can use regex capture groups to pass the specific path segment to your callback. For example, (/\d*) will pass the array index as the second argument to your closure.

    Stopping the Parser

    To improve performance when you only need a specific piece of data, you can call $parser->stop() inside your callback to halt the parsing process immediately.

    // Basic usage: selecting specific paths
    $listener = new RegexListener([
        "/1/name" => function ($data) {
            echo "/1/name=" . $data . PHP_EOL;
        }
    ]);
    $parser = new Parser(fopen($filename, 'rb'), $listener);
    $parser->parse();
    
    // Using capture groups to get the path segment
    $listener = new RegexListener([
        "(/\d*)" => function ($data, $path) {
            echo $path . "=" . $data['name'] . PHP_EOL;
        }
    ]);
    $parser = new Parser(fopen($filename, 'rb'), $listener);
    $parser->parse();
    
    // Stopping the parser once data is found
    $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();
        }
    ]);
    $parser->parse();
  5. Use SimpleObjectQueueListener for parsing arrays of simple objects

    master

    The SimpleObjectQueueListener is a specialized listener designed to parse simple JSON files containing a flat array of objects (e.g., [{"id":"1", "name":"foo"}, {"id":"2", "name":"bar"}]). It is ideal for high-volume data imports or database seeding where memory efficiency is critical.

    Limitations:

    • It only supports single-level, non-nested objects.
    • It does not support nested arrays or nested objects out of the box.

    To use it, instantiate the listener with a callback function that will be executed every time an object in the array is fully parsed.

    use JsonStreamingParser\Listener
    SimpleObjectQueueListener;
    
    // Define a callback to handle each parsed object
    $callback = function ($object) {
        // Process the object (e.g., save to database)
        echo "Parsed object: " . print_r($object, true) . "\n";
    };
    
    // Initialize the listener. 
    // Use TYPE_ARRAY to receive associative arrays, or TYPE_OBJECT to receive stdClass objects.
    $listener = new SimpleObjectQueueListener($callback, SimpleObjectQueueListener::TYPE_ARRAY);
    
    // The $listener would then be passed to the Parser instance
  6. Use GeoJsonListener to parse GeoJSON features one by one

    master

    The GeoJsonListener is a specialized implementation of ListenerInterface designed to handle GeoJSON data efficiently. Instead of building a massive in-memory FeatureCollection, it constructs an in-memory representation only at the second level of the JSON hierarchy. This allows you to process individual Feature objects one at a time, significantly reducing memory usage when parsing large GeoJSON files.

    To use it, pass a callable to the constructor. This callback will be triggered every time a single feature (or a second-level object) is fully parsed. The callback receives the parsed object as its argument.

    use JsonStreamingParserormatormat; // Assuming parser usage
    use JsonStreamingParserormatormat;
    use JsonStreamingParser\Listener\GeoJsonListener;
    
    // The callback is executed for every second-level object parsed
    $callback = function ($feature) {
        // Process a single GeoJSON Feature here
        echo "Parsed feature: " . json_encode($feature) . PHP_EOL;
    };
    
    $listener = new GeoJsonListener($callback);
    
    // Pass the listener to your parser instance to begin streaming
    // $parser->parse($stream, $listener);
  7. Use InMemoryListener to build a full JSON object in memory

    master

    The InMemoryListener is a basic implementation of a listener that constructs a complete in-memory representation of a JSON document. While streaming parsers are typically used to avoid loading entire documents into memory, this class is useful as a reference implementation or as a starting point for building custom listeners.

    After the parser has finished, you can retrieve the fully parsed JSON structure using the getJson() method.

    // Example usage pattern (conceptual)
    $listener = new \JsonStreamingParser\Listener\InMemoryListener();
    $parser = new \JsonStreamingParser\Parser($listener);
    $parser->parse($jsonString);
    
    $json = $listener->getJson();
  8. Configure RegexListener matches

    master

    The RegexListener uses a mapping of regular expression strings to callables to determine which parts of the JSON stream to act upon.

    setMatch(array $dataMatch)

    Sets or updates the matching criteria.

    Parameters:

    • $dataMatch: An associative array where the key is a regular expression string representing the JSON path, and the value is a callable (e.g., an anonymous function) that receives the extracted data.

    Callback Signature: function ($value, $pathSegment = [])

    • $value: The data found at the matched path.
    • $pathSegment: (Optional) If the regex contains a capture group, this argument contains the captured string/array.
    $listener = new RegexListener();
    $listener->setMatch([
        "/user/id" => function($id) { /* ... */ },
        "/items/\d+/price" => function($price) { /* ... */ }
    ]);
  9. Configure SimpleObjectQueueListener return types

    master

    When instantiating SimpleObjectQueueListener, you can specify the data type that the callback receives using the $returnType parameter. This determines whether the parsed object is passed to your callback as an associative array or a stdClass object.

    Available constants:

    • SimpleObjectQueueListener::TYPE_ARRAY: Provides an associative array to the callback.
    • SimpleObjectQueueListener::TYPE_OBJECT: Provides an object (stdClass) to the callback.
    // To receive associative arrays:
    $listener = new SimpleObjectQueueListener($callback, SimpleObjectQueueListener::TYPE_ARRAY);
    
    // To receive objects:
    $listener = new SimpleObjectQueueListener($callback, SimpleObjectQueueListener::TYPE_OBJECT);
  10. Initialize the Parser

    master

    To use the streaming parser, instantiate the JsonStreamingParser\Parser class. You must provide a PHP stream resource and an implementation of ListenerInterface which will receive events as the JSON is parsed.

    Optional parameters allow you to configure the line ending, whether to emit whitespace events, and the buffer size for reading from the stream.

    use JsonStreamingParser\Parser;
    use Your\Custom\ListenerImplementation;
    
    $stream = fopen('data.json', 'r');
    $listener = new ListenerImplementation();
    
    $parser = new Parser(
        $stream,           // resource
        $listener,         // ListenerInterface
        "\n",             // string (optional, default: "\n")
        false,             // bool (optional, default: false, emitWhitespace)
        8192               // int (optional, default: 8192, bufferSize)
    );