symfony/json-streamer
Symfony JsonStreamer reads and writes data structures to and from JSON streams efficiently. Ideal for streaming large JSON payloads with low memory usage, integrating with Symfony Serializer to parse or generate JSON incrementally.
Installation:
composer require symfony/json-streamer
Ensure your project uses PHP 8.4+ (Laravel 12+ recommended).
First Use Case: Stream a large JSON response from a Laravel controller:
use Symfony\Component\Serializer\Encoder\JsonStreamEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
public function streamJsonResponse()
{
$encoder = new JsonStreamEncoder();
$normalizer = new ObjectNormalizer();
$serializer = new Serializer([$normalizer], [$encoder]);
$resource = new \stdClass(); // Replace with your data structure
$resource->largeArray = range(1, 1_000_000); // Simulate large data
return response()->stream(
fn () => $serializer->stream($resource, 'json_stream')
);
}
Key Classes to Explore:
JsonStreamEncoder: Core encoder for streaming JSON.ObjectNormalizer: Maps objects to JSON (customize with context or groups).Serializer: Combines normalizers and encoders.JsonStreamer: Low-level API for advanced use cases (e.g., custom value transformers).Documentation: Start with Symfony’s official docs and focus on:
json_stream format).circular_reference_handler).Workflow:
json_stream format to avoid loading entire payloads into memory.Example:
public function streamPaginatedData()
{
$serializer = $this->getSerializer(); // Configured with JsonStreamEncoder
$data = $this->fetchLargeDataset(); // Generator or iterable
return response()->stream(
fn () => $serializer->stream($data, 'json_stream', [
'json_encode_options' => JSON_PRETTY_PRINT,
])
);
}
Laravel Integration:
Symfony\Component\HttpFoundation\StreamedResponse for fine-grained control:
$response = new StreamedResponse();
$response->setCallback(fn () => $serializer->stream($data, 'json_stream'));
return $response;
Workflow:
JsonStreamEncoder in reverse to parse incrementally.Example:
use Symfony\Component\Serializer\Encoder\JsonStreamDecoder;
public function processStreamingJson()
{
$decoder = new JsonStreamDecoder();
$stream = fopen('large.json', 'r'); // Or a network stream
$decoder->decodeStream($stream, 'json_stream', function ($data) {
// Process each chunk as it arrives
$this->handleChunk($data);
});
}
Use Cases:
Workflow:
ObjectNormalizer to handle domain-specific logic (e.g., encrypting fields, formatting dates).normalizer->setValueTransformer().Example:
use Symfony\Component\Serializer\Normalizer\ValueTransformerInterface;
class EncryptedFieldTransformer implements ValueTransformerInterface
{
public function transform($value, string $format = null, array $context = [])
{
return $value ? base64_encode($value) : null;
}
public function supportsTransformation($data, string $format = null, array $context = [])
{
return is_string($data) && isset($context['encrypt']);
}
}
// Usage:
$normalizer = new ObjectNormalizer();
$normalizer->setValueTransformer(new EncryptedFieldTransformer());
$serializer = new Serializer([$normalizer], [$encoder]);
Laravel-Specific:
App\Services\Encryption or Carbon for seamless integration.Workflow:
circular_reference_handler to avoid infinite loops in self-referencing objects.Example:
use Symfony\Component\Serializer\Normalizer\CircularReferenceHandlerInterface;
class LaravelCircularReferenceHandler implements CircularReferenceHandlerInterface
{
public function getReplacement(array $data, string $format, array $context)
{
return ['$ref' => spl_object_hash($data)];
}
public function getResolved(array $data, string $format, array $context)
{
return null; // Or resolve via Laravel's cache
}
}
// Usage:
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceHandler(new LaravelCircularReferenceHandler());
Workflow:
Example:
use Symfony\Component\Serializer\Annotation\Synthetic;
class User
{
#[Synthetic]
public function getFullName(): string
{
return "{$this->firstName} {$this->lastName}";
}
}
Laravel Integration:
$normalizer->setSyntheticPropertyGenerator(
fn (object $object, string $format, array $context) => [
'full_name' => "{$object->first_name} {$object->last_name}",
]
);
PHP 8.4+ Requirement:
composer.json overrides or CI checks:
"config": {
"platform-check": true,
"platform": {
"php": "8.4.0"
}
}
Memory Leaks in Early Versions:
composer update symfony/json-streamer
Circular References Without Handler:
SerializationException if not configured. Test with:
$user = new User();
$user->self = $user; // Circular reference
$serializer->serialize($user, 'json_stream'); // Fails without handler
Null Property Handling:
include_null_properties: false). Explicitly enable:
$encoder->setIncludeNullProperties(true);
Generator vs. Iterable:
yield) work best; arrays or Iterator may cause memory spikes.Validate JSON Output:
Use JsonStreamEncoder::decode() to verify round-trip serialization:
$encoded = $serializer->serialize($data, 'json_stream');
$decoded = $serializer->deserialize($encoded, get_class($data), 'json_stream');
Log Stream Chunks: Intercept chunks for debugging:
$serializer->stream($data, 'json_stream', [], function ($chunk) {
\Log::debug('Stream chunk:', ['chunk' => $chunk]);
});
Check Cache Issues:
php artisan cache:clear
Performance Profiling:
memory_get_usage():
$start = memory_get_usage();
$serializer->stream($data, 'json_stream');
$end = memory_get_usage();
\Log::info("Memory used: " . ($end - $start) / 1024 / 1024 . "MB");
Custom Encoders:
Extend JsonStreamEncoder for domain-specific formats (e.g., Avro, Protobuf):
class CustomJsonStreamEncoder extends JsonStreamEncoder
{
protected function encode($data, string $format, array $context)
{
// Custom logic
}
}
Attribute-Based Configuration:
Use Symfony’s #[Groups] or #[SerializedName] with Laravel’s HasAttributes:
use Symfony\Component\Serializer\Annotation\Groups;
class Product
{
#[Groups(['api'])]
public string $name;
}
Laravel Service Provider: Bind the serializer to the container for reuse:
// app/Providers/AppServiceProvider.php
public function register
How can I help you explore Laravel packages today?