Mathias Arlaud
Co-Founder & COO @Bakslash - Co-Founder & CTO @Synegram
Robin Chalas
@bakslashHQ
@chalas_r
Mathias Arlaud
@bakslashHQ
@matarld
Representing data structures in a format that can be sent or persisted in order to be reconstructed later
Binary, textual
Construction pattern
Databases, flat files, APIs
Anywhere, interoperable
@chalas_r
@matarld
https://symfony.com/doc/current/components/serializer.html
@chalas_r
@matarld
{ "name":"Axel", "color":"Red", "age":2 }
array(3) { ["name"]=> string(4) "Axel" ["color"]=> string(3) "Red" ["age"]=> int(2) }
object(Cat)#1 (3) { ["name"]=> string(4) "Axel" ["color"]=> string(3) "Red" ["age"]=> int(2) }
@chalas_r
@matarld
@chalas_r
@matarld
@chalas_r
@matarld
AbstractNormalizer
allows attributes
instantiates objects
AbstractObjectNormalizer
normalization
denormalization
validation
ObjectNormalizer
extracts attributes
BC policy
Reflection is slow
Improved a lot over PHP versions, but still... Caching is needed.
@chalas_r
@matarld
@chalas_r
@matarld
class Pet
{
#[ORM\Column(type: 'boolean')]
#[Groups([
'cat_list:read',
'cat:read',
'cat:write'
])]
private bool $meow = false;
#[ORM\Column(type: 'boolean')]
#[Groups([
'dog_list:read',
'dog:read',
'dog:write'
])]
private bool $bark = false;
}
@chalas_r
@matarld
class Cat
{
private bool $meow = false;
}
class Dog
{
private bool $bark = false;
}
@chalas_r
@matarld
class ListCat
{
private string $name;
private bool $meow;
}
class ReadCat
{
private string $name;
private float $height;
private float $weight;
}
Metadata is data
@chalas_r
@matarld
, but about data
data
metadata
"thing"
"animal"
"cat"
"flying cat"
@chalas_r
@matarld
data
/** @temlate T of string */
final class Rain {
/** @return list<T> */
public function content(): array {...}
}
/** @var Rain<'cat'|'dog'> $*/
$englishRain = new Rain();
$fallingStuff = $englishRain->content();
type
no idea
array of something
list of something
list of cats and dogs
Possible JSON
[
[0.7]
]
{
"c": 1
}
true
{
"a": 1
}
[
3.14
]
[
"pi"
]
[ true ]
[
3.14
]
[
"dog"
]
[ "cat" ]
[
"dog"
]
[
"dog"
]
@chalas_r
@matarld
/**
* @template T of Animal
*/
final class FlyingAnimal
{
public function __construct(
public object $type,
private int $speed,
) {
}
/**
* @return class-string<T>
*/
public function getTypeClass(): string
{
return $this->type::class;
}
public function setSpeed(int $speed): void
{
$this->speed = $speed;
}
}
PropertyInfo
@chalas_r
@matarld
@chalas_r
@matarld
/**
* @template T of Animal
*/
final class FlyingAnimal
{
public function __construct(
public object $type,
private int $speed,
) {
}
/**
* @return class-string<T>
*/
public function getTypeClass(): string
{
return $this->type::class;
}
public function setSpeed(int $speed): void
{
$this->speed = $speed;
}
}
TypeInfo
@chalas_r
@matarld
@chalas_r
@matarld
Huge collection of objects
Super big array
array
@chalas_r
@matarld
Huge collection of objects
array
@chalas_r
@matarld
@chalas_r
@matarld
PHP
metadata
Cat::class
class Cat
{
public string $name;
public bool $flying;
}
// ...
$this->jsonEncoder->encode($cat);
Possible JSON
{ "name": "any_string", "flying: true|false }
@chalas_r
@matarld
Encoder
Possible JSON
[ { "name": "any_string", "flying: true|false },
{ "name": "any_string", "flying: true|false }
]
return static function ($cats, $stream, $config) {
$stream->write('[');
$prefix_0 = '';
foreach ($cats as $cat) {
$stream->write($prefix_0);
$stream->write('{"name":');
$stream->write(json_encode($cat->name));
$stream->write(',"flying":');
$stream->write($cat->flying ? 'true' : 'false');
$stream->write('}');
$prefix_0 = ',';
}
$stream->write(']');
};
@chalas_r
@matarld
I want to serialize a cat!
Does an encoder exist?
/\_____/\ / o o \ ( == ^ == ) ) ( ( ) ( ( ) ( ) ) (__(__)___(__)__)
Encode the cat
Yep 👌
Compute the cat JSON shape
Generate and store the encoder
No
@chalas_r
@matarld
Read only needed
JSON part lazily
Blazing fast!
Flat memory usage
Stream ready
Generics ready
Simple API
Edit data on-the-fly
__ o-''|\_____/) \_/|_) ) \ __ / (_/ (_/
@chalas_r
@matarld
@chalas_r
@matarld
composer req jolicode/automapper
__ o-''|\_____/) \_/|_) ) \ __ / (_/ (_/
@chalas_r
@matarld
use AutoMapper\Attribute as Mapper;
final class Cat
{
#[MapTo(
target: Dog::class,
property: 'barkVolume',
)]
public int $meowVolume;
}
final class Dog
{
public int $barkVolume;
}
barkVolume
meowVolume
@chalas_r
@matarld
use AutoMapper\Attribute as Mapper;
final class Cat
{
#[MapTo(
target: Dog::class,
transformer: [self::class, 'toDogAge'],
)]
public int $age;
public static function toDogAge(int $value, Source $source, array $context): int
{
return floor($value * 1.3);
}
}
@chalas_r
@matarld
final class Cat
{
#[MapTo(
target: Dog::class,
transformer: CatToDogAgeTransformer::class,
)]
public int $age;
}
class CatToDogAgeTransformer implements PropertyTransformerInterface
{
public function __construct(
private AgeConverter $ageConverter,
) {}
public function transform(int $value, object|array $source, array $context): mixed
{
return $this->ageConverter->fromCatToDog($value);
}
}
use Symfony\Component\Serializer\SerializerInterface;
final readonly class MyService
{
public function __construct(
private SerializerInterface $serializer,
) {
}
}
@chalas_r
@matarld
// config/bundles.php
return [
// ...
Mtarld\JsonEncoderBundle\JsonEncoderBundle::class => ['all' => true],
AutoMapper\Bundle\AutoMapperBundle::class => ['all' => true],
TurboSerializer\TurboSerializerBundle::class => ['all' => true],
];
composer require korbeil/turbo-serializer
@chalas_r
@matarld
use Symfony\Component\Serializer\SerializerInterface;
use TurboSerializer\Serializer;
final readonly class ApiController
{
public function __construct(
private SerializerInterface $turboSerializer,
) {
}
#[Route('/api/cats')]
public function get(): Response
{
$cats = $this->getCats();
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
return new Response($this->turboSerializer->serialize(
$cats,
'json',
[Serializer::TYPE => $type]
));
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private SerializerInterface $turboSerializer,
) {
}
#[Route('/api/create-cats')]
public function post(string $content): Response
{
$cats = $this->turboSerializer->deserialize($content, Cat::class.'[]', 'json');
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private SerializerInterface $turboSerializer,
) {
}
#[Route('/api/cats-but-dogs')]
public function get(): Response
{
$cats = $this->getCats();
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
return new Response($this->turboSerializer->serialize(
$cats,
'json',
[Serializer::NORMALIZED_TYPE => $type]
));
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private SerializerInterface $turboSerializer,
) {
}
#[Route('/api/create-cats')]
public function post(string $content): Response
{
$dogsType = Type::iterable(Type::object(Dog::class), Type::int(), asList: true);
$this->turboSerializer->deserialize(
$content,
Cat::class.[],
'json',
[Serializer::NORMALIZED_TYPE => $dogsType],
);
// ...
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private EncoderInterface $jsonEncoder,
) {
}
#[Route('/api/cats')]
public function get(): Response
{
$cats = $this->getCats();
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
return new Response((string) $this->jsonEncoder->encode($cats, $type));
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private DecoderInterface $jsonDecoder,
) {
}
#[Route('/api/create-cats')]
public function post(string $content): Response
{
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
$cats = $this->jsonDecoder->decode($content, $type);
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private EncoderInterface $jsonEncoder,
) {
}
#[Route('/api/cats')]
public function get(): Response
{
$cats = $this->getCats();
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
return new StreamedResponse(function () use ($cats, $type): void {
foreach ($this->jsonEncoder->encode($cats, $type) as $chunk) {
echo $chunk;
}
});
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private EncoderInterface $jsonEncoder,
) {
}
#[Route('/api/cats')]
public function get(): Response
{
$cats = $this->getCats();
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
$this->jsonEncoder->encode($cats, $type, ['stream' => new OutputStream()]);
return new Response();
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private DecoderInterface $jsonDecoder,
) {
}
#[Route('/api/create-cats')]
public function post(): Response
{
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
$cats = $this->jsonDecoder->decode(new InputStream(), $type);
foreach ($cats as $i => $cat) {
if ($i === 1000) {
dd($cat->name);
}
}
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private EncoderInterface $jsonEncoder,
private AutoMapperInterface $autoMapper,
) {
}
#[Route('/api/cats-but-dogs')]
public function get(): Response
{
$cats = $this->getCats();
$type = Type::iterable(Type::object(Dog::class), Type::int(), asList: true);
$this->jsonEncoder->encode($this->catsToDogs($cats), $type, ['stream' => new OutputStream()]);
return new Response();
}
private function catsToDogs(iterable $cats): iterable
{
foreach ($cats as $cat) {
yield $this->autoMapper->map($cat, Dog::class);
}
}
}
@chalas_r
@matarld
final readonly class ApiController
{
public function __construct(
private DecoderInterface $jsonDecoder,
private AutomapperInterface $mapper,
) {
}
#[Route('/api/create-cats')]
public function post(): Response
{
$type = Type::iterable(Type::object(Cat::class), Type::int(), asList: true);
$this->catsToDogs($this->jsonDecoder->decode(new InputStream(), $type));
}
private function catsToDogs(iterable $cats): iterable
{
foreach ($cats as $cat) {
yield $this->autoMapper->map($cat, Dog::class);
}
}
}
@chalas_r
@matarld
Deserialization
Serializer
JsonEncoder
JsonEncoder
+ Automapper
Serialization
JsonEncoder
Serializer
JsonEncoder
+ Automapper
@chalas_r
@matarld
https://github.com/mtarld/json-encoder-bundle
https://github.com/jolicode/automapper
https://github.com/korbeil/turbo-serializer
https://github.com/korbeil/turbo-serializer-bench
Robin Chalas
@bakslashHQ
@chalas_r
Mathias Arlaud
@bakslashHQ
@matarld
By Mathias Arlaud