salsify/json-streaming-parser
Streaming JSON parser for PHP that processes huge JSON documents without loading them into memory. SAX-style event callbacks via a Listener interface, PSR compliant, installable with Composer. Ideal for large files and low-memory environments.
Install the package:
composer require salsify/json-streaming-parser
Create a listener implementing \JsonStreamingParser\Listener\ListenerInterface:
use JsonStreamingParser\Listener\ListenerInterface;
class MyJsonListener implements ListenerInterface {
public function enterObject(array $properties) {}
public function leaveObject() {}
public function enterArray() {}
public function leaveArray() {}
public function key($key) {}
public function value($value) {}
public function complete() {}
}
Parse a JSON file:
$stream = fopen('large_file.json', 'r');
$parser = new \JsonStreamingParser\Parser($stream, new MyJsonListener());
$parser->parse();
fclose($stream);
Use this package when dealing with JSON files too large to load into memory (e.g., >100MB). Example:
// Process a 500MB JSON file without memory overload
$listener = new class implements ListenerInterface {
public function value($value) {
// Process each value as it's parsed (e.g., write to DB)
}
};
$parser = new \JsonStreamingParser\Parser(fopen('huge_data.json', 'r'), $listener);
$parser->parse();
Streaming Pipeline:
$stream = fopen('data.json', 'r');
$listener = new MyListener();
$parser = new \JsonStreamingParser\Parser($stream, $listener);
$parser->parse(); // Triggers listener callbacks
fclose($stream);
Listener Patterns:
class MyListener implements ListenerInterface {
private $depth = 0;
public function enterObject(array $properties) { $this->depth++; }
public function leaveObject() { $this->depth--; }
}
public function key($key) {
if ($key === 'metadata') return; // Skip metadata
}
Integration with Laravel:
$this->app->bind('json.parser', function() {
return new \JsonStreamingParser\Parser(
fopen(storage_path('app/large.json'), 'r'),
new MyListener()
);
});
ParseLargeJsonJob::dispatch('path/to/file.json');
class ParseLargeJsonJob implements ShouldQueue {
protected $filePath;
public function handle() {
$parser = new \JsonStreamingParser\Parser(
fopen($this->filePath, 'r'),
new MyListener()
);
$parser->parse();
}
}
Error Handling:
try-catch to handle malformed JSON:
try {
$parser->parse();
} catch (\JsonStreamingParser\Exception\ParsingException $e) {
Log::error("JSON parse error at position {$e->getPosition()}: {$e->getMessage()}");
}
Position Tracking (v8.0+):
PositionAwareInterface to log error positions:
class MyListener implements ListenerInterface, PositionAwareInterface {
public function setFilePosition($position) {
$this->currentPosition = $position;
}
}
Resource Leaks:
fclose() streams in finally blocks or use Laravel's Storage facade:
$stream = fopen(storage_path('app/data.json'), 'r');
try {
$parser->parse();
} finally {
fclose($stream);
}
Laravel\Filesystem\Filesystem::get() for automatic cleanup:
$stream = Storage::get('path/to/file.json');
$parser = new \JsonStreamingParser\Parser(fopen('php://temp', 'r+'), $listener);
fwrite($stream, $parser->getStream());
UTF-8 BOM Issues:
$stream = fopen('file.json', 'r');
$bom = pack('H*', 'EFBBBF');
if (fread($stream, 3) === $bom) {
rewind($stream);
}
Nested Structures:
enter/leave calls.private $depth = 0;
public function enterObject(array $properties) { $this->depth++; }
public function leaveObject() { $this->depth--; }
Large Arrays/Objects:
private $batch = [];
public function value($value) {
$this->batch[] = $value;
if (count($this->batch) >= 100) {
DB::table('data')->insert($this->batch);
$this->batch = [];
}
}
PHP 8.0+ Features:
public function value(string $value) { /* ... */ }
Log Events:
class DebugListener implements ListenerInterface {
public function enterObject(array $properties) {
Log::debug('Enter object', ['properties' => $properties]);
}
// Implement all other methods similarly
}
Test with Small Files:
{"key": "value"}
Check for Trailing Commas:
if (preg_match('/[,\[\]{}]\s*,\s*[^\s]/', file_get_contents('file.json'))) {
throw new \Exception('Trailing comma detected!');
}
Custom Exceptions:
\JsonStreamingParser\Exception\ParsingException for domain-specific errors:
class InvalidDataException extends ParsingException {}
Parser-Aware Listeners (v8.1+):
ParserAwareInterface to access parser state:
class MyListener implements ListenerInterface, ParserAwareInterface {
public function setParser(\JsonStreamingParser\Parser $parser) {
$this->parser = $parser;
}
}
Position Tracking:
setFilePosition() to log line/column numbers:
public function setFilePosition($position) {
$this->line = substr_count($this->rawContent, "\n", 0, $position) + 1;
}
Stream Wrappers:
fopen() wrappers:
$parser = new \JsonStreamingParser\Parser(
fopen('https://example.com/large.json', 'r'),
$listener
);
How can I help you explore Laravel packages today?