Le pipeline serialize/normalize/encode
Serializer est une pile en 3 couches — normalizer (objet → tableau), encoder (tableau → string). Désérialiser c'est l'inverse.
Le Serializer Symfony n'est pas une boîte noire — c'est une pile à trois couches empilées dans l'ordre. Comprendre cette pile, c'est comprendre pourquoi chaque option se configure où elle se configure.
Appelle d'abord
normalize() pour obtenir un tableau PHP (ou un scalaire),
puis encode() pour convertir ce tableau en string. C'est le point d'entrée
de haut niveau que tu utilises dans tes controllers.
Appelle d'abord
decode() pour obtenir un tableau depuis la string,
puis denormalize() pour reconstruire l'objet PHP. L'inverse exact de serialize().
Les trois couches
objet → tableau
tableau → string
↕ inverse
string → tableau
tableau → objet
Les interfaces
| Interface | Méthodes clés | Rôle |
|---|---|---|
SerializerInterface |
serialize($data, $format, $context)deserialize($data, $type, $format, $context) |
Point d'entrée haut niveau |
NormalizerInterface |
normalize($data, $format, $context)supportsNormalization() |
Objet → tableau/scalaire |
DenormalizerInterface |
denormalize($data, $type, $format, $context)supportsDenormalization() |
Tableau → objet |
EncoderInterface |
encode($data, $format, $context)supportsEncoding($format) |
Tableau → string |
DecoderInterface |
decode($data, $format, $context)supportsDecoding($format) |
String → tableau |
Formats disponibles
Symfony fournit quatre encoders prêts à l'emploi : json (JsonEncoder),
xml (XmlEncoder), csv (CsvEncoder),
yaml (YamlEncoder, requiert symfony/yaml).
Lance la simulation pour voir les étapes s'exécuter dans l'ordre :
Clique sur Sérialiser pour visualiser les étapes.
NormalizerInterface et first-match
Un normalizer transforme un objet PHP en données normalisées — il est sélectionné par le premier supportsNormalization() vrai.
Quand tu appelles $serializer->normalize($monObjet), le Serializer parcourt
sa liste de normalizers dans l'ordre et interroge chacun : "Est-ce que tu sais normaliser cet objet
dans ce format ?" Le premier qui répond true est utilisé — c'est la
first-match strategy.
Deux méthodes obligatoires :
normalize(mixed $data, ?string $format, array $context): array|string|int|float|bool|\ArrayObject|null
et supportsNormalization(mixed $data, ?string $format, array $context): bool.
La troisième méthode, getSupportedTypes(?string $format): array, est optionnelle
mais recommandée pour le cache de support.
getSupportedTypes() — le cache de support
Depuis Symfony 6.3, getSupportedTypes() permet au Serializer de mémoriser
quels types un normalizer supporte, sans appeler supportsNormalization() à chaque fois :
public function getSupportedTypes(?string $format): array
{
return [
User::class => true, // toujours supporté, résultat mis en cache
Product::class => true,
'object' => false, // supporte les objets, mais résultat PAS mis en cache
'*' => false, // wildcard : supporte tout, sans cache
];
}
true vs false : true signifie
"ce normalizer supporte ce type et le résultat de supportsNormalization()
peut être mis en cache". false signifie "supporte ce type mais il faut appeler
supportsNormalization() à chaque fois" (utile quand la réponse dépend du contexte).
Ordre de priorité
L'ordre dans lequel les normalizers sont consultés est déterminé par le tag
serializer.normalizer avec l'attribut priority.
Un priority plus élevé est consulté en premier.
# config/services.yaml
App\Serializer\MonNormalizer:
tags:
- { name: serializer.normalizer, priority: 200 }
# ObjectNormalizer a une priorité de -1000 : il est le dernier recours
DenormalizerInterface
L'interface miroir pour la dénormalisation, avec deux constantes importantes pour la collecte des erreurs :
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
// Collecter toutes les erreurs de type au lieu de lever une exception au premier
$data = $serializer->deserialize($json, User::class, 'json', [
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
]);
// Collecter les attributs en trop (non présents dans la classe cible)
$data = $serializer->deserialize($json, User::class, 'json', [
DenormalizerInterface::COLLECT_EXTRA_ATTRIBUTES_ERRORS => true,
]);
PartialDenormalizationException, dont tu peux extraire la liste via
$e->getErrors().
Les normalizers fournis par Symfony
Symfony fournit une bibliothèque de normalizers spécialisés — chacun cible un type PHP précis.
Plutôt que de tout implémenter depuis zéro, commence toujours par vérifier si un normalizer fourni couvre ton besoin. Symfony en fournit une douzaine — chacun est spécialisé sur un type PHP précis.
ObjectNormalizer — le couteau suisse
ObjectNormalizer est le normalizer le plus polyvalent. Il utilise le composant
PropertyAccess pour lire les propriétés : getters (getName()),
propriétés publiques, accès is/has… dans cet ordre. C'est lui qui
est utilisé par défaut pour les entités Doctrine et les DTOs.
// Symfony configure automatiquement ObjectNormalizer avec PropertyAccess.
// Pour une classe sans getter :
class User {
public int $id;
public string $name;
}
// → normalize() retourne ['id' => 42, 'name' => 'Alice']
GetSetMethodNormalizer vs PropertyNormalizer
| Normalizer | Stratégie d'accès | Cas d'usage |
|---|---|---|
GetSetMethodNormalizer |
Getters/setters uniquement (getName() → clé name) |
Classes Java-style avec accesseurs explicites |
PropertyNormalizer |
Propriétés (publiques + protected + private via reflection) | Value objects avec propriétés privées directes |
ObjectNormalizer |
PropertyAccess (getters, puis props publiques) | Cas général — choix par défaut |
Normalizers spécialisés
Normalise
\DateTimeInterface en string et inversement.
Options : datetime_format (ex. 'Y-m-d\TH:i:sP'),
datetime_timezone (DateTimeZone).
Par défaut, utilise RFC 3339.
Normalise les backed enums PHP 8.1 (
Status::Active → 'active').
Dénormalise la valeur brute vers l'enum ('active' → Status::Active).
Lève InvalidArgumentException si la valeur n'existe pas dans l'enum.
Normalise les
AbstractUid Symfony (UUID, ULID).
Option uid_normalization_format : 'canonical' (RFC 4122),
'base32', 'base58', 'rfc4122'.
Dénormalise
T[] depuis un tableau de tableaux.
Usage : passer User[]::class comme type dans deserialize().
Ce normalizer est automatiquement consulté dès que le type se termine par [].
Délègue à
jsonSerialize() pour les classes qui implémentent
JsonSerializable. Utile pour les classes qui gèrent elles-mêmes leur
représentation JSON (ex. Money, Collection maison).
Guide de choix
| Type PHP | Normalizer à utiliser |
|---|---|
| Entité Doctrine / DTO avec getters | ObjectNormalizer (défaut) |
\DateTimeInterface | DateTimeNormalizer |
| Backed enum PHP 8.1 | BackedEnumNormalizer |
| UUID / ULID Symfony | UidNormalizer |
Tableau d'objets T[] | ArrayDenormalizer |
Classe implémentant JsonSerializable | JsonSerializableNormalizer |
| Violation de contrainte Validator | ConstraintViolationListNormalizer |
Encoders — JSON, XML, CSV, YAML
Un encoder transforme les données normalisées en string — chaque format a ses options contextuelles.
Un encoder ne voit que les données normalisées — un tableau PHP ou un scalaire. Il n'a aucune connaissance des objets. C'est pourquoi les options de format se passent toutes dans le contexte, non dans la configuration du service.
JsonEncoder
$serializer->serialize($data, 'json', [
'json_encode_options' => JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE,
'json_decode_associative' => false, // decode en stdClass au lieu de tableau
'json_decode_recursion_depth' => 512, // profondeur max de décodage
]);
Accepte les flags PHP
json_encode() combinés avec |.
Flags utiles : JSON_PRETTY_PRINT, JSON_UNESCAPED_UNICODE,
JSON_UNESCAPED_SLASHES, JSON_THROW_ON_ERROR (déjà activé par défaut
dans Symfony 6+), JSON_PRESERVE_ZERO_FRACTION.
XmlEncoder
$serializer->serialize($data, 'xml', [
'xml_root_node_name' => 'response', // nœud racine (défaut: 'response')
'xml_format_output' => true, // indenter le XML
'xml_remove_empty_tags' => true, // supprimer les balises vides
'xml_encoding' => 'UTF-8', // encodage déclaré dans
'xml_version' => '1.0',
]);
<item id="1">, préfixe les clés
de ton tableau avec @ : ['@id' => 1, '#' => 'contenu'].
Le # est la clé du nœud texte.
CsvEncoder
$serializer->serialize($rows, 'csv', [
'csv_delimiter' => ';', // séparateur (défaut: ',')
'csv_enclosure' => '"', // caractère d'encadrement
'csv_headers' => ['id', 'name'], // colonnes dans l'ordre
'no_headers' => false, // omettre la ligne d'en-tête
'csv_key_separator' => '.', // séparateur pour tableaux imbriqués
]);
Le CSV est plat par nature. Si tu as un objet avec des sous-objets, le
CsvEncoder
aplatit les clés avec le csv_key_separator : un objet
{ user: { name: 'Alice' } } devient la colonne user.name.
YamlEncoder
$serializer->serialize($data, 'yaml', [
'yaml_inline' => 2, // niveau à partir duquel on passe en style inline
'yaml_indent' => 4, // indentation
'yaml_flags' => 0, // flags Yaml::dump()
]);
Le YamlEncoder requiert le composant symfony/yaml. Il est enregistré
automatiquement si le composant est installé.
Attributs de sérialisation
Les attributs PHP 8 déclarent directement sur la classe comment chaque propriété doit être sérialisée.
Plutôt que de configurer les normalizers dans des fichiers YAML séparés, les attributs PHP 8 permettent de déclarer la sérialisation directement sur la classe. C'est la façon recommandée depuis Symfony 6.
#[Groups]
use Symfony\Component\Serializer\Attribute\Groups;
class User
{
#[Groups(['public', 'admin'])]
public int $id;
#[Groups(['public'])]
public string $name;
#[Groups(['admin'])]
public string $email;
}
#[SerializedName]
Renomme la clé dans la sortie sérialisée, sans toucher au nom de la propriété PHP :
#[SerializedName('first_name')]
public string $firstName;
// → {"first_name": "Alice"} au lieu de {"firstName": "Alice"}
#[Ignore]
Exclut complètement une propriété de la sérialisation et de la dénormalisation :
#[Ignore]
private string $password;
// → la propriété n'apparaît jamais dans la sortie JSON
#[MaxDepth]
Limite la profondeur de sérialisation pour une propriété d'objet imbriqué.
Requiert enable_max_depth => true dans le contexte :
#[MaxDepth(1)]
public Category $category;
// → la Category est sérialisée, mais ses sous-catégories non
#[Context]
Surcharge le contexte global pour une propriété spécifique. Utile pour des formats de date différents selon les propriétés :
#[Context(normalizationContext: ['datetime_format' => 'd/m/Y'])]
public \DateTimeImmutable $birthdate;
#[Context(normalizationContext: ['datetime_format' => 'Y-m-d\TH:i:sP'])]
public \DateTimeImmutable $createdAt;
#[SerializedPath]
Mappe une propriété vers un chemin imbriqué dans la sortie — ou désimbrique lors de la dénormalisation :
#[SerializedPath('[owner][name]')]
public string $ownerName;
// → {"owner": {"name": "Alice"}} au lieu de {"ownerName": "Alice"}
Symfony\Component\Serializer\Attribute\ (sans le Annotation).
L'ancien namespace Symfony\Component\Serializer\Annotation\ reste valide
pour la compatibilité, mais préfère le nouveau.
Active ou désactive les attributs ci-dessous pour voir comment la sortie JSON évolue :
Sortie JSON résultante :
Groupes de sérialisation
Les groupes permettent de contrôler quelles propriétés sont sérialisées selon le contexte — API publique vs admin vs interne.
Imagine une entité User avec 10 propriétés. Ton API publique en expose 3.
Ton back-office en expose 8. Tes logs internes en consomment 2.
Les groupes de sérialisation te permettent de gérer ces trois vues sans dupliquer
la classe — juste en passant le bon groupe dans le contexte.
Déclarer les groupes sur les propriétés
use Symfony\Component\Serializer\Attribute\Groups;
class User
{
#[Groups(['public', 'admin'])]
public int $id;
#[Groups(['public'])]
public string $name;
#[Groups(['admin'])]
public string $email;
#[Groups(['internal'])]
public string $passwordHash;
// pas de #[Groups] → sérialisée uniquement si aucun groupe dans le contexte
public \DateTimeImmutable $createdAt;
}
Passer les groupes dans le contexte
// API publique — expose id + name
$serializer->serialize($user, 'json', ['groups' => ['public']]);
// Back-office — expose id + name + email
$serializer->serialize($user, 'json', ['groups' => ['public', 'admin']]);
// Aucun groupe → TOUTES les propriétés (y compris sans #[Groups])
$serializer->serialize($user, 'json');
Dès qu'un groupe est spécifié dans le contexte, seules les propriétés portant
#[Groups] avec au moins un groupe correspondant sont sérialisées.
Les propriétés sans #[Groups] sont ignorées.
En l'absence de tout groupe dans le contexte, toutes les propriétés sont sérialisées.
normalizationContext vs denormalizationContext
Dans les controllers Symfony, tu peux différencier les groupes selon la direction :
// Avec #[Context] au niveau de la propriété :
#[Context(
normalizationContext: ['groups' => ['public']],
denormalizationContext: ['groups' => ['admin']],
)]
public string $role;
snake_case
et préfixe-les par le contexte métier (api:public, api:admin,
webhook:payload). Ça évite les collisions entre bundles et rend le code
auto-documenté.
Coche les groupes actifs pour voir quelles propriétés sont incluses dans la sortie :
Propriétés incluses :
Contexte — les clés importantes
Le contexte est un tableau passé à chaque couche du serializer — il contrôle le comportement fin de chaque normalizer.
Le contexte est le troisième argument de serialize() / normalize().
C'est un simple tableau PHP transmis à toutes les couches de la pile.
Chaque normalizer et encoder lit les clés qui le concernent et ignore les autres.
Gestion des valeurs nulles et non-initialisées
// Omettre les propriétés nulles
$serializer->serialize($user, 'json', [
'skip_null_values' => true,
]);
// {"id":42,"name":"Alice"} — 'email' null omis
// Omettre les propriétés PHP non initialisées (PHP 8.0+)
$serializer->serialize($dto, 'json', [
'skip_uninitialized_values' => true,
]);
// Garder {} pour les objets vides (au lieu de [])
$serializer->serialize($data, 'json', [
'preserve_empty_objects' => true,
]);
object_to_populate — mise à jour partielle
Lors de la dénormalisation, tu peux cibler un objet existant à mettre à jour au lieu d'en créer un nouveau. Idéal pour les endpoints PATCH :
$existingUser = $repository->find($id);
$updatedUser = $serializer->deserialize($json, User::class, 'json', [
'object_to_populate' => $existingUser,
]);
// $existingUser est modifié en place — aucune nouvelle instance créée
callbacks — transformer une propriété à la volée
Un callback reçoit la valeur normalisée et peut la transformer. Utile sans avoir besoin d'un normalizer custom :
$serializer->serialize($user, 'json', [
'callbacks' => [
'birthdate' => fn(\DateTimeInterface $d) => $d->format('d/m/Y'),
'tags' => fn(array $tags) => implode(', ', $tags),
],
]);
ignored_attributes et allow_extra_attributes
// Exclure des propriétés sans modifier la classe
$serializer->serialize($user, 'json', [
'ignored_attributes' => ['password', 'internalNotes'],
]);
// Désactiver le rejet des attributs inconnus lors de la dénormalisation
// (par défaut : true — les attributs inconnus sont ignorés silencieusement)
$serializer->deserialize($json, User::class, 'json', [
'allow_extra_attributes' => false, // lève une exception si attribut inconnu
]);
disable_type_enforcement
// Désactiver la vérification de type lors de la dénormalisation
// Utile pour les formulaires où les types arrivent en string
$serializer->deserialize($formData, User::class, 'json', [
'disable_type_enforcement' => true,
]);
skip_null_values), configure-le dans framework.yaml :
serializer: default_context: { skip_null_values: true }.
Références circulaires et MaxDepth
Quand un objet se référence lui-même, le serializer boucle — deux mécanismes existent pour gérer ça.
Tu as un objet Category avec une propriété $parent qui est aussi
une Category, elle-même avec un $parent… et ainsi de suite.
Le Serializer va boucler jusqu'à une CircularReferenceException — à moins que
tu ne configures l'un des deux mécanismes de protection.
circular_reference_handler
Un callable appelé dès qu'une référence circulaire est détectée. La valeur retournée remplace l'objet dans la sortie :
$serializer->serialize($category, 'json', [
'circular_reference_handler' => fn(object $obj) => $obj->getId(),
]);
// La Category parente est remplacée par son ID dans la sortie
Nombre de fois qu'un même objet peut être sérialisé dans le même arbre avant que le handler soit déclenché. Défaut :
1. Augmenter à 2 pour
les structures qui se réfèrent légitimement à elles-mêmes une fois (ex. arbre binaire
avec nœud gauche = nœud courant dans certains algorithmes).
$serializer->serialize($data, 'json', [
'circular_reference_limit' => 2,
'circular_reference_handler' => fn(object $obj) => $obj->getId(),
]);
#[MaxDepth] — tronquer à N niveaux
#[MaxDepth] ne résout pas une boucle infinie — il la prévient en limitant
la profondeur de sérialisation à N niveaux pour une propriété donnée.
Il faut activer enable_max_depth dans le contexte :
use Symfony\Component\Serializer\Attribute\MaxDepth;
class Category
{
public string $name;
#[MaxDepth(2)]
public ?Category $parent = null;
}
$serializer->serialize($category, 'json', [
'enable_max_depth' => true,
]);
max_depth_handler — contrôler la valeur tronquée
Par défaut, quand la profondeur max est atteinte, la valeur est null.
Tu peux retourner autre chose via max_depth_handler :
$serializer->serialize($category, 'json', [
'enable_max_depth' => true,
'max_depth_handler' => function(object $obj, string $prop, array $context): mixed {
return ['id' => $obj->getId(), '_truncated' => true];
},
]);
- Détecte une boucle infinie à l'exécution
- Déclenché quand l'objet A → B → A est détecté
- La valeur retournée remplace l'objet circulaire
- Fonctionne sans modifier les entités
- Limite déclarée statiquement sur la propriété
- Tronque à N niveaux, qu'il y ait boucle ou non
- Requiert
enable_max_depthdans le contexte - Contrôle plus fin via
max_depth_handler
#[MaxDepth(2)]
sur les relations potentiellement profondes pour éviter les sérialisations accidentellement
longues, et configure circular_reference_handler comme filet de sécurité.
Custom normalizer et DiscriminatorMap
Quand aucun normalizer fourni ne convient, on en crée un — et pour la désérialisation polymorphique, DiscriminatorMap câble tout.
Quand aucun normalizer fourni ne couvre ton besoin — type complexe, logique métier dans la transformation, injection de service — tu crées le tien en quelques dizaines de lignes.
Structure d'un normalizer custom
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class MoneyNormalizer implements NormalizerInterface
{
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
/** @var Money $data */
return [
'amount' => $data->getAmount(),
'currency' => $data->getCurrency()->getCode(),
];
}
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof Money;
}
public function getSupportedTypes(?string $format): array
{
// true = résultat de supportsNormalization() mis en cache pour ce type
return [Money::class => true];
}
}
Symfony autoconfigure les services implémentant
NormalizerInterface avec le tag
serializer.normalizer. Tu n'as rien à déclarer dans services.yaml.
Pour contrôler la priorité : #[AsTaggedItem(priority: 100)] sur la classe.
NormalizerAwareInterface — déléguer les sous-objets
Si l'objet que tu normalises contient lui-même des sous-objets, injecte le serializer pour les déléguer :
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
final class OrderNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
public function normalize(mixed $data, ?string $format = null, array $context = []): array
{
return [
'id' => $data->getId(),
// Déléguer les lignes de commande au normalizer approprié
'lines' => array_map(
fn($line) => $this->normalizer->normalize($line, $format, $context),
$data->getLines()->toArray()
),
];
}
}
#[DiscriminatorMap] — désérialisation polymorphique
Quand un champ JSON peut représenter plusieurs types PHP différents (par exemple
un shape qui peut être un Circle ou un Square),
#[DiscriminatorMap] câble automatiquement la résolution :
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
#[DiscriminatorMap(
typeProperty: 'type',
mapping: [
'circle' => Circle::class,
'square' => Square::class,
'triangle' => Triangle::class,
]
)]
abstract class Shape {}
final class Circle extends Shape
{
public function __construct(public readonly float $radius) {}
}
// Désérialisation :
$shape = $serializer->deserialize(
'{"type":"circle","radius":5.0}',
Shape::class,
'json'
);
// → instance de Circle avec $radius = 5.0
La clé JSON qui contient le discriminant (
"type" dans l'exemple).
Cette clé est lue avant la dénormalisation — elle n'apparaît pas nécessairement
comme propriété dans la classe cible.
defaultType: SomeClass::class pour
gérer les entrées dont la valeur du discriminant n'est pas dans le mapping, au lieu
de lever une exception. Utile pour les migrations de format où d'anciens documents
n'ont pas encore le champ type.
Synthèse — le guide de choix
Le guide de choix et les anti-patterns du Serializer Symfony.
Le Serializer Symfony est un système extensible avec de nombreuses options. Voici les règles de décision et les erreurs classiques à éviter.
Guide de choix — quel normalizer ?
| Type PHP à normalizer | Normalizer recommandé |
|---|---|
| Entité Doctrine, DTO avec getters/props publiques | ObjectNormalizer (défaut automatique) |
\DateTimeInterface / \DateTimeImmutable |
DateTimeNormalizer |
| Backed enum PHP 8.1+ | BackedEnumNormalizer |
| UUID / ULID Symfony | UidNormalizer |
Tableau d'objets User[] |
ArrayDenormalizer (automatique pour T[]) |
Classe implémentant JsonSerializable |
JsonSerializableNormalizer |
ConstraintViolationList du Validator |
ConstraintViolationListNormalizer |
| Type custom ou logique complexe | Custom normalizer (implements NormalizerInterface) |
| Hiérarchie polymorphique | ObjectNormalizer + #[DiscriminatorMap] |
Anti-patterns à éviter
json_encode() directement sur une entité Doctrine.Les entités ont des proxies Doctrine, des collections lazy, des relations circulaires potentielles. Passer par le Serializer est la seule façon correcte : il gère les groupes, les profondeurs et les types complexes.
getSupportedTypes() dans un normalizer custom.Sans cette méthode,
supportsNormalization() est appelée à chaque sérialisation
pour chaque objet. Avec elle, le résultat est mis en cache dès le premier appel.
C'est une ligne de code pour un gain de performance mesurable.
L'ordre des normalizers dans le DIC sans priorité dépend de l'ordre de chargement des services. C'est non déterministe entre environnements. Toujours déclarer une
priority
sur le tag serializer.normalizer de tes normalizers custom.
enable_max_depth sans #[MaxDepth] sur les propriétés.enable_max_depth sans attributs ne fait rien — la profondeur n'est pas limitée
par défaut. Il faut les deux : l'option dans le contexte ET l'attribut sur la propriété
concernée.
Récapitulatif des attributs
| Attribut | Cible PHP | Rôle |
|---|---|---|
#[Groups(string|array)] | Propriété, méthode | Appartenance à des groupes de sérialisation |
#[SerializedName(string)] | Propriété, méthode | Renommer la clé dans la sortie |
#[Ignore] | Propriété, méthode | Exclure complètement de la sérialisation |
#[MaxDepth(int)] | Propriété | Limiter la profondeur de sérialisation |
#[Context(...)] | Propriété, méthode | Contexte spécifique à une propriété |
#[SerializedPath(string)] | Propriété | Mapper vers/depuis un chemin imbriqué |
#[DiscriminatorMap(typeProperty, mapping)] | Classe abstraite | Désérialisation polymorphique |