The Serializer component transforms data structures from formats like JSON, XML, etc. to PHP objects and vice versa. It offers great flexibility and advanced features, such as dynamically reshaping objects using normalizers and denormalizers or handling complex objects with multiple serialization representations.
However, this flexibility comes at a cost in terms of performance and memory usage. While often acceptable, it becomes a bottleneck in applications that deal exclusively with JSON and large volumes of data.
That's why Symfony 7.3 introduces JsonStreamer: a new component focused on blazing-fast JSON encoding and decoding with minimal memory usage. In our internal benchmarks, this component serializes 10,000 objects 10x faster and uses 50% less memory than the Serializer component. It also deserializes 50,000 objects 10x faster with 90% less memory usage.
Encoding PHP Objects into JSON
To encode PHP classes into JSON, define public properties (like typical DTOs),
and annotate the class with the #[JsonStreamable]
attribute:
1 2 3 4 5 6 7 8 9 10 11
namespace App\Dto;
use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
#[JsonStreamable]
class User
{
public string $name;
public int $age;
public string $email;
}
Next, inject the main encoding service by type-hinting your service argument
with Symfony\Component\JsonStreamer\StreamWriterInterface
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// src/Controller/UserController.php
namespace App\Controller;
use App\Dto\User;
use App\Repository\UserRepository;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\JsonStreamer\StreamWriterInterface;
use Symfony\Component\TypeInfo\Type;
class UserController
{
public function retrieveUsers(StreamWriterInterface $jsonStreamWriter, UserRepository $userRepository): StreamedResponse
{
// find the user objects somehow...
$users = $userRepository->findAll();
// describe the data type of this collection of objects
$type = Type::list(Type::object(User::class));
// pass the collection of objects and their data type...
$json = $jsonStreamWriter->write($users, $type);
// ...and stream the results
return new StreamedResponse($json);
}
}
Before encoding the PHP objects, you must define their types using the
TypeInfo component. In this example, the repository returns a list<User>
,
so it's described as Type::list(Type::object(User::class))
.
JsonStreamer uses PHP generators internally to keep memory usage low and enable streaming as data is being encoded. This means you can encode thousands or even millions of objects efficiently, without running into memory limits or long wait times.
Decoding JSON into PHP Objects
Deserializing JSON into PHP classes is similarly straightforward:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// src/Controller/UserController.php
namespace App\Controller;
use App\Dto\User;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\TypeInfo\Type;
class UserController
{
// ...
public function retrieveUsers(StreamReaderInterface $jsonStreamReader, UserRepository $userRepository): Response
{
// get the JSON contents somehow (API call, reading a file, etc.)
$jsonResource = fopen($this->jsonFilePath, 'r');
// describe the data type of this collection of objects
$type = Type::iterable(Type::object(User::class));
// decode the JSON content into a list of PHP objects...
/** @var iterable<User> $users */
$users = $jsonStreamReader->read($jsonResource, $type);
foreach ($users as $user) {
// iterate over the User objects and do something with them
}
// ...
}
}
Alternatively, if memory is not a concern, you can load and decode everything at once:
1 2 3 4 5 6 7 8 9 10
// read the entire JSON content to memory
$json = file_get_contents($this->jsonFilePath);
$type = Type::list(Type::object(User::class));
/** @var list<User> $users */
$users = $this->jsonStreamReader->read($json, $type);
// if $type = Type::iterable(Type::object(User::class));
// then you need to do the following to get the full list of objects:
// $userObjects = iterator_to_array($users);
Advanced Features
Although introduced as an experimental feature, JsonStreamer already supports
a rich set of features. You can customize property names in the output. For
example, to serialize an id
property as @id
:
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
use use Symfony\Component\JsonStreamer\Attribute\StreamedName;
#[JsonStreamable]
class User
{
#[StreamedName('@id')]
public string $id;
// ...
}
You can also apply value transformations during encoding and decoding using PHP functions, class methods, or services:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
use Symfony\Component\JsonStreamer\Attribute\ValueTransformer;
#[JsonStreamable]
class User
{
#[ValueTransformer(nativeToStream: 'strtoupper')]
public string $bloodType;
#[ValueTransformer(nativeToStream: [self::class, 'formatHeight'])]
public int $height;
// ...
public static function formatHeight(int $value, array $options = []): string
{
return sprintf('%.2fcm', $value / 100);
}
}
Despite its experimental status, JsonStreamer is already being integrated in projects like API Platform. In their tests, JsonStreamer cut serialization time from 83% to 45% of total request time, while reducing memory usage by over 50%.
JsonStreamer is one of the three new components introduced in Symfony 7.3. The others are JsonPath (to query and extract data from JSON) and ObjectMapper (to transfer data between objects).
In the last example in "Decoding JSON into PHP Objects" there is incorrect variable name is used.
$json
have to be used instead of$jsonResource
.But on the component itself - it is really great new addition to the Symfony ecosystem!
@Alexander we just fixed the wrong variable names in the examples. Thanks!