# Symfony JSON Streamer Component ## Introduction The Symfony JSON Streamer component provides powerful methods to read and write data structures from and into JSON streams with memory-efficient processing. This experimental component is designed to handle large JSON datasets by streaming data incrementally rather than loading entire documents into memory. It leverages PHP's type system and Symfony's TypeInfo component to generate optimized stream processors at runtime, enabling high-performance serialization and deserialization of complex object graphs. The component supports advanced features including custom value transformers for data conversion, property name mapping through attributes, generic type handling, DateTime conversions, lazy instantiation for streamed resources, and comprehensive error handling. It generates PHP code at runtime that is cached for subsequent uses, making repeated operations extremely fast while maintaining the flexibility to handle diverse data structures including scalars, collections, objects with nested properties, enums, and union types. ## APIs and Functions ### JsonStreamReader::create() - Create a JSON Stream Reader Creates a new JSON stream reader instance with optional custom value transformers and cache directories for generated reader code and lazy ghost proxies. ```php new StringToDateTimeValueTransformer() ], streamReadersDir: '/tmp/my_app/stream_readers', lazyGhostsDir: '/tmp/my_app/lazy_ghosts' ); // Example class to deserialize class User { public int $id; public string $name; public string $email; } // Read from a JSON string $json = '{"id": 42, "name": "John Doe", "email": "john@example.com"}'; $user = $reader->read($json, Type::object(User::class)); echo $user->name; // Output: John Doe ``` ### JsonStreamReader::read() - Read JSON into Typed Objects Reads JSON input (string or resource) and converts it into strongly-typed PHP objects, collections, or scalars according to the specified type definition. ```php read('true', Type::bool()); $integer = $reader->read('42', Type::int()); $nullValue = $reader->read('null', Type::nullable(Type::string())); // Read arrays and lists $array = $reader->read('[1, 2, 3, 4, 5]', Type::list(Type::int())); $dict = $reader->read('{"key1": "value1", "key2": "value2"}', Type::dict(Type::string())); // Read from a stream resource for large files $fileHandle = fopen('/path/to/large-dataset.json', 'r'); try { $data = $reader->read($fileHandle, Type::list(Type::object(User::class))); foreach ($data as $user) { // Process users lazily without loading entire file processUser($user); } } finally { fclose($fileHandle); } // Read nested collections $json = '[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]'; $users = $reader->read($json, Type::list(Type::object(User::class))); ``` ### JsonStreamWriter::create() - Create a JSON Stream Writer Creates a new JSON stream writer instance with optional custom value transformers and a cache directory for generated writer code. ```php new DateTimeToStringValueTransformer() ], streamWritersDir: '/tmp/my_app/stream_writers' ); ``` ### JsonStreamWriter::write() - Write Objects to JSON Stream Writes PHP data structures to JSON format as a traversable, memory-efficient stream of string chunks that can be iterated or converted to a complete string. ```php write($product, Type::object(Product::class)); echo $json; // {"id":100,"name":"Laptop","price":999.99} // Iterate through chunks for streaming output $result = $writer->write($product, Type::object(Product::class)); foreach ($result as $chunk) { echo $chunk; // Stream output chunk by chunk } // Write collections $products = [new Product(), new Product()]; $json = (string) $writer->write($products, Type::list(Type::object(Product::class))); // Write with options $dateTime = new \DateTimeImmutable('2024-11-20'); $json = (string) $writer->write( $dateTime, Type::object(\DateTimeImmutable::class), ['date_time_format' => 'Y-m-d'] ); ``` ### StreamedName Attribute - Map Property Names Defines custom JSON property names that differ from the PHP property names using the StreamedName attribute. ```php read($json, Type::object(ApiResponse::class)); echo $response->id; // 123 echo $response->username; // johndoe // Write back with custom field names $output = (string) $writer->write($response, Type::object(ApiResponse::class)); echo $output; // {"@id":123,"user_name":"johndoe","email":"john@example.com"} ``` ### ValueTransformer Attribute - Custom Value Transformation Apply custom transformations to property values during serialization and deserialization using the ValueTransformer attribute with callables or service IDs. ```php strtoupper($value), nativeToStream: fn(string $value) => strtolower($value) )] public string $code; #[ValueTransformer( streamToNative: fn(string $value) => (int)($value / 100), nativeToStream: fn(int $value) => (string)($value * 100) )] public int $priceInDollars; } $reader = JsonStreamReader::create(); $writer = JsonStreamWriter::create(); // Reading transforms "abc" to "ABC" $json = '{"id": 1, "code": "abc", "priceInDollars": "500"}'; $product = $reader->read($json, Type::object(Product::class)); echo $product->code; // ABC echo $product->priceInDollars; // 5 // Writing transforms "XYZ" to "xyz" $product->code = 'XYZ'; $product->priceInDollars = 10; $output = (string) $writer->write($product, Type::object(Product::class)); echo $output; // {"id":1,"code":"xyz","priceInDollars":"1000"} ``` ### ValueTransformerInterface - Implement Custom Transformers Create reusable value transformer classes by implementing the ValueTransformerInterface for complex transformation logic. ```php new CurrencyTransformer(), CentsToStringTransformer::class => new CentsToStringTransformer() ] ); $json = '{"id": 42, "totalInCents": "$149.99"}'; $order = $reader->read($json, Type::object(Order::class)); echo $order->totalInCents; // 14999 (cents) ``` ### Generic Types - Handle Parameterized Collections Read and write collections with specific element types using generic type annotations for type-safe operations on templated classes. ```php */ public array $items = []; public int $total = 0; public int $page = 1; } $reader = JsonStreamReader::create(); $writer = JsonStreamWriter::create(); // Read generic collection with specific type parameter $json = '{"items": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}], "total": 2, "page": 1}'; $result = $reader->read( $json, Type::generic(Type::object(PagedResult::class), Type::object(User::class)) ); foreach ($result->items as $user) { echo $user->name . "\n"; // Alice, Bob } // Write generic collection $pagedResult = new PagedResult(); $pagedResult->items = [new User(), new User()]; $pagedResult->total = 2; $output = (string) $writer->write( $pagedResult, Type::generic(Type::object(PagedResult::class), Type::object(User::class)) ); ``` ### DateTime Handling - Automatic Date Conversion Built-in transformers automatically handle conversion between JSON strings and PHP DateTime objects with customizable formats. ```php read($json, Type::object(Event::class)); echo $event->startDate->format('Y-m-d'); // 2024-11-20 // Read dates with custom format $json2 = '{"id": 2, "startDate": "2024-12-25", "endDate": "2024-12-26", "title": "Holiday"}'; $event2 = $reader->read( $json2, Type::object(Event::class), [DateTimeToStringValueTransformer::FORMAT_KEY => 'Y-m-d'] ); // Write dates with custom format $event->startDate = new \DateTimeImmutable('2025-01-01'); $event->endDate = new \DateTimeImmutable('2025-01-02'); $output = (string) $writer->write( $event, Type::object(Event::class), [DateTimeToStringValueTransformer::FORMAT_KEY => 'Y-m-d'] ); echo $output; // {"id":1,"startDate":"2025-01-01","endDate":"2025-01-02","title":"Conference"} ``` ### Nullable and Union Types - Handle Complex Types Support for nullable properties and union types allows flexible data models that can handle multiple value types or null values. ```php read($json1, Type::object(Profile::class)); var_dump($profile1->bio); // NULL // Read with mixed types in union property $json2 = '{"id": 2, "bio": "Developer", "status": 1, "metadata": "text"}'; $profile2 = $reader->read($json2, Type::object(Profile::class)); echo $profile2->metadata; // "text" $json3 = '{"id": 3, "bio": "Designer", "status": 1, "metadata": 42}'; $profile3 = $reader->read($json3, Type::object(Profile::class)); echo $profile3->metadata; // 42 // Write nullable values $profile = new Profile(); $profile->id = 4; $profile->bio = null; $output = (string) $writer->write($profile, Type::object(Profile::class)); echo $output; // {"id":4,"bio":null,"status":null,"metadata":null} ``` ### Stream Processing - Memory-Efficient Large File Handling Process large JSON files efficiently by reading from file handles, enabling lazy loading and iteration without consuming excessive memory. ```php read($fileHandle, Type::iterable(Type::object(LogEntry::class))); $errorCount = 0; foreach ($logs as $entry) { if ($entry->level === 'ERROR') { $errorCount++; echo "Error at {$entry->timestamp}: {$entry->message}\n"; } // Memory is freed after each iteration if ($errorCount >= 10) { break; // Stop after finding 10 errors } } } finally { fclose($fileHandle); } // Write large datasets efficiently $writer = JsonStreamWriter::create(); $output = $writer->write( generateLargeDataset(), // Generator function Type::list(Type::object(LogEntry::class)) ); // Stream directly to output or file foreach ($output as $chunk) { echo $chunk; // Or: fwrite($outputFile, $chunk); } ``` ## Summary The Symfony JSON Streamer component is designed for applications that need to process large JSON datasets efficiently, such as data pipelines, ETL processes, API clients consuming paginated endpoints, log processors, and data migration tools. It excels in scenarios where memory consumption is a concern, as it can handle files of arbitrary size by streaming data incrementally. The component is particularly valuable for microservices that exchange complex data structures, batch processing systems, and any application requiring high-performance JSON serialization with strong type safety. Integration with existing Symfony applications is straightforward through dependency injection, with support for cache warming to pre-generate optimized stream processors during deployment. The component works seamlessly with Symfony's type system and can be extended with custom value transformers for domain-specific data conversions. Common integration patterns include configuring cache directories for production environments, registering custom transformers as services, using attributes to map between API contracts and domain models, and combining with Symfony's HTTP client for efficient API consumption. The generated stream processors are cached and reused across requests, ensuring minimal overhead after the initial code generation.