# Symfony Serializer
The Symfony Serializer component is a powerful PHP library for converting complex data structures and object graphs into various formats including JSON, XML, CSV, and YAML, and vice versa. It provides a two-step process: normalization (converting objects to arrays) and encoding (converting arrays to specific formats), enabling seamless transformation between PHP objects and serialized data.
The component supports advanced features including serialization groups for controlling which properties are serialized, name converters for transforming property names between formats, circular reference handling, max depth control, and discriminator maps for polymorphic deserialization. It integrates tightly with Symfony's ecosystem and supports PHP 8 attributes for declarative configuration of serialization behavior.
## Serializer Class
The main `Serializer` class provides the entry point for all serialization operations. It combines normalizers and encoders to transform data between PHP objects and serialized formats, supporting multiple output formats and customizable context options.
```php
serialize($user, 'json');
// Output: {"name":"John Doe","email":"john@example.com","createdAt":"2024-01-15T00:00:00+00:00"}
// Serialize to XML
$xml = $serializer->serialize($user, 'xml');
// Output: John Doejohn@example.com...
// Deserialize JSON back to object
$userData = '{"name":"Jane Doe","email":"jane@example.com","createdAt":"2024-02-20T10:30:00+00:00"}';
$deserializedUser = $serializer->deserialize($userData, User::class, 'json');
// $deserializedUser is a User object with the provided data
```
## ObjectNormalizer
The `ObjectNormalizer` is the most versatile normalizer, using the PropertyAccess component to handle objects with public properties, getters/setters, and magic methods. It automatically detects accessible properties and converts them to/from arrays.
```php
name; }
public function setName(string $name): void { $this->name = $name; }
public function getPrice(): float { return $this->price; }
public function setPrice(float $price): void { $this->price = $price; }
public function isInStock(): bool { return $this->inStock; }
public function setInStock(bool $inStock): void { $this->inStock = $inStock; }
}
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
$product = new Product();
$product->setName('Widget');
$product->setPrice(29.99);
$product->setInStock(true);
// Serialize - automatically uses getters
$json = $serializer->serialize($product, 'json');
// Output: {"name":"Widget","price":29.99,"inStock":true}
// Deserialize - automatically uses setters
$data = '{"name":"Gadget","price":49.99,"inStock":false}';
$product = $serializer->deserialize($data, Product::class, 'json');
// $product->getName() returns "Gadget"
```
## Groups Attribute
The `#[Groups]` attribute controls which properties are included in serialization based on group names. This enables different representations of the same object for different API endpoints or use cases.
```php
id = $id;
$this->title = $title;
$this->content = $content;
$this->internalNotes = $internalNotes;
}
}
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$serializer = new Serializer(
[new ObjectNormalizer($classMetadataFactory)],
[new JsonEncoder()]
);
$article = new Article(1, 'Hello World', 'Full article content here...', 'Internal review pending');
// Serialize with 'list' group - only id and title
$listJson = $serializer->serialize($article, 'json', ['groups' => ['list']]);
// Output: {"id":1,"title":"Hello World"}
// Serialize with 'detail' group - id, title, and content
$detailJson = $serializer->serialize($article, 'json', ['groups' => ['detail']]);
// Output: {"id":1,"title":"Hello World","content":"Full article content here..."}
// Serialize with multiple groups
$adminJson = $serializer->serialize($article, 'json', ['groups' => ['detail', 'admin']]);
// Output: {"id":1,"title":"Hello World","content":"Full article content here...","internalNotes":"Internal review pending"}
```
## SerializedName Attribute
The `#[SerializedName]` attribute allows customizing the property name used in the serialized output, enabling compatibility with different naming conventions in external APIs.
```php
userId = $userId;
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->emailAddress = $emailAddress;
}
}
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$serializer = new Serializer(
[new ObjectNormalizer($classMetadataFactory)],
[new JsonEncoder()]
);
$response = new ApiResponse(42, 'John', 'Doe', 'john.doe@example.com');
// Serialize uses custom names
$json = $serializer->serialize($response, 'json');
// Output: {"user_id":42,"first_name":"John","last_name":"Doe","email_address":"john.doe@example.com"}
// Deserialize also recognizes custom names
$data = '{"user_id":100,"first_name":"Jane","last_name":"Smith","email_address":"jane@example.com"}';
$obj = $serializer->deserialize($data, ApiResponse::class, 'json');
// $obj->userId = 100, $obj->firstName = "Jane"
```
## CamelCaseToSnakeCaseNameConverter
The `CamelCaseToSnakeCaseNameConverter` automatically converts between camelCase PHP property names and snake_case serialized names, useful for API compatibility without individual attribute annotations.
```php
userId = $userId;
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->emailAddress = $emailAddress;
$this->createdAt = $createdAt;
}
}
$nameConverter = new CamelCaseToSnakeCaseNameConverter();
$serializer = new Serializer(
[new ObjectNormalizer(null, $nameConverter)],
[new JsonEncoder()]
);
$profile = new UserProfile(1, 'John', 'Doe', 'john@example.com', new \DateTime());
// All properties automatically converted to snake_case
$json = $serializer->serialize($profile, 'json');
// Output: {"user_id":1,"first_name":"John","last_name":"Doe","email_address":"john@example.com","created_at":"..."}
// Deserialize from snake_case back to camelCase properties
$data = '{"user_id":2,"first_name":"Jane","last_name":"Smith","email_address":"jane@example.com","created_at":"2024-01-01T00:00:00+00:00"}';
$profile = $serializer->deserialize($data, UserProfile::class, 'json');
// $profile->userId = 2, $profile->firstName = "Jane"
```
## Ignore Attribute
The `#[Ignore]` attribute excludes specific properties from serialization entirely, useful for sensitive data or internal state that should never be exposed.
```php
id = $id;
$this->username = $username;
$this->passwordHash = $passwordHash;
$this->securityToken = $securityToken;
$this->email = $email;
}
}
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$serializer = new Serializer(
[new ObjectNormalizer($classMetadataFactory)],
[new JsonEncoder()]
);
$account = new Account(1, 'johndoe', 'hashed_password_123', 'secret_token_abc', 'john@example.com');
// Sensitive fields are excluded from serialization
$json = $serializer->serialize($account, 'json');
// Output: {"id":1,"username":"johndoe","email":"john@example.com"}
// passwordHash and securityToken are NOT included
```
## MaxDepth Attribute
The `#[MaxDepth]` attribute controls how deep the serializer traverses nested objects, preventing infinite loops and controlling output size for deeply nested structures.
```php
id = $id;
$this->name = $name;
}
}
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$serializer = new Serializer(
[new ObjectNormalizer($classMetadataFactory)],
[new JsonEncoder()]
);
// Create nested category structure
$root = new Category(1, 'Electronics');
$computers = new Category(2, 'Computers');
$laptops = new Category(3, 'Laptops');
$gaming = new Category(4, 'Gaming Laptops');
$root->children = [$computers];
$computers->parent = $root;
$computers->children = [$laptops];
$laptops->parent = $computers;
$laptops->children = [$gaming];
$gaming->parent = $laptops;
// Enable max depth handling in context
$json = $serializer->serialize($root, 'json', [
'enable_max_depth' => true
]);
// Output includes up to 2 levels of nesting, then stops
// {"id":1,"name":"Electronics","parent":null,"children":[{"id":2,"name":"Computers",...}]}
```
## DateTimeNormalizer
The `DateTimeNormalizer` handles serialization and deserialization of DateTime objects with customizable format and timezone support.
```php
title = $title;
$this->startDate = $startDate;
$this->endDate = $endDate;
}
}
// Default RFC3339 format
$serializer = new Serializer(
[new DateTimeNormalizer(), new ObjectNormalizer()],
[new JsonEncoder()]
);
$event = new Event(
'Conference',
new \DateTime('2024-06-15 09:00:00'),
new \DateTime('2024-06-15 17:00:00')
);
$json = $serializer->serialize($event, 'json');
// Output: {"title":"Conference","startDate":"2024-06-15T09:00:00+00:00","endDate":"2024-06-15T17:00:00+00:00"}
// Custom date format
$customSerializer = new Serializer(
[new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i']), new ObjectNormalizer()],
[new JsonEncoder()]
);
$customJson = $customSerializer->serialize($event, 'json');
// Output: {"title":"Conference","startDate":"2024-06-15 09:00","endDate":"2024-06-15 17:00"}
// With timezone conversion
$tzSerializer = new Serializer(
[new DateTimeNormalizer([
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i T',
DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('America/New_York')
]), new ObjectNormalizer()],
[new JsonEncoder()]
);
```
## JsonEncoder
The `JsonEncoder` handles JSON encoding and decoding with support for PHP's json_encode/json_decode options through context parameters.
```php
title = 'Sales Report';
$report->data = ['q1' => 1000, 'q2' => 1500, 'q3' => 1200, 'q4' => 1800];
$report->description = "Annual sales with special chars: <>&\"'";
// Default JSON encoding
$json = $serializer->serialize($report, 'json');
// Output: {"title":"Sales Report","data":{"q1":1000,"q2":1500,"q3":1200,"q4":1800},"description":"Annual sales with special chars: <>&\"'"}
// Pretty-printed JSON with options
$prettyJson = $serializer->serialize($report, 'json', [
JsonEncode::OPTIONS => JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
]);
// Output is formatted with indentation
// Decode JSON with associative arrays
$jsonData = '{"name":"Test","values":[1,2,3]}';
$decoded = $serializer->decode($jsonData, 'json');
// $decoded = ['name' => 'Test', 'values' => [1, 2, 3]]
```
## XmlEncoder
The `XmlEncoder` serializes data to XML format with configurable root node name, CDATA wrapping, and attribute handling.
```php
title = 'The Great Gatsby';
$book->author = 'F. Scott Fitzgerald';
$book->year = 1925;
$book->description = 'A story about & the American Dream';
// Default XML encoding
$xml = $serializer->serialize($book, 'xml');
// Output: The Great Gatsby...
// Custom root node name
$xml = $serializer->serialize($book, 'xml', [
XmlEncoder::ROOT_NODE_NAME => 'book'
]);
// Output: The Great Gatsby...
// Formatted output with encoding
$xml = $serializer->serialize($book, 'xml', [
XmlEncoder::ROOT_NODE_NAME => 'book',
XmlEncoder::FORMAT_OUTPUT => true,
XmlEncoder::ENCODING => 'UTF-8'
]);
// Decode XML to array
$xmlData = '1984George Orwell';
$decoded = $serializer->decode($xmlData, 'xml');
// $decoded = ['title' => '1984', 'author' => 'George Orwell']
```
## CsvEncoder
The `CsvEncoder` handles CSV encoding and decoding with support for custom delimiters, headers, and nested data flattening.
```php
1, 'name' => 'John Doe', 'department' => 'Engineering', 'salary' => 75000],
['id' => 2, 'name' => 'Jane Smith', 'department' => 'Marketing', 'salary' => 65000],
['id' => 3, 'name' => 'Bob Wilson', 'department' => 'Engineering', 'salary' => 80000],
];
// Default CSV encoding
$csv = $serializer->serialize($employees, 'csv');
// Output:
// id,name,department,salary
// 1,"John Doe",Engineering,75000
// 2,"Jane Smith",Marketing,65000
// 3,"Bob Wilson",Engineering,80000
// Custom delimiter and enclosure
$csv = $serializer->serialize($employees, 'csv', [
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
]);
// Output uses semicolons and single quotes
// Without headers
$csv = $serializer->serialize($employees, 'csv', [
CsvEncoder::NO_HEADERS_KEY => true
]);
// Decode CSV
$csvData = "id,name,department\n1,Alice,Sales\n2,Bob,IT";
$decoded = $serializer->decode($csvData, 'csv');
// $decoded = [['id' => '1', 'name' => 'Alice', 'department' => 'Sales'], ...]
```
## YamlEncoder
The `YamlEncoder` serializes data to YAML format with configurable inline depth and YAML flags, requiring the symfony/yaml component.
```php
environment = 'production';
$config->database = [
'host' => 'localhost',
'port' => 5432,
'name' => 'myapp',
'credentials' => ['user' => 'admin', 'password' => 'secret']
];
$config->cache = ['driver' => 'redis', 'ttl' => 3600];
// Default YAML encoding (expanded format)
$yaml = $serializer->serialize($config, 'yaml');
// Output:
// environment: production
// database:
// host: localhost
// port: 5432
// name: myapp
// credentials:
// user: admin
// password: secret
// cache:
// driver: redis
// ttl: 3600
// Inline format (more compact)
$yaml = $serializer->serialize($config, 'yaml', [
YamlEncoder::YAML_INLINE => 2 // Inline at depth 2
]);
// Decode YAML
$yamlData = "name: MyApp\nversion: 1.0\nfeatures:\n - auth\n - api";
$decoded = $serializer->decode($yamlData, 'yaml');
// $decoded = ['name' => 'MyApp', 'version' => '1.0', 'features' => ['auth', 'api']]
```
## DiscriminatorMap Attribute
The `#[DiscriminatorMap]` attribute enables polymorphic deserialization, allowing the serializer to instantiate the correct subclass based on a type field in the data.
```php
Car::class,
'motorcycle' => Motorcycle::class,
'truck' => Truck::class,
])]
abstract class Vehicle
{
public string $brand;
public string $model;
}
class Car extends Vehicle
{
public int $doors;
public string $fuelType;
}
class Motorcycle extends Vehicle
{
public int $engineCC;
public bool $hasSidecar;
}
class Truck extends Vehicle
{
public float $cargoCapacity;
public int $axles;
}
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$serializer = new Serializer(
[new ObjectNormalizer($classMetadataFactory)],
[new JsonEncoder()]
);
// Deserialize to specific subclass based on "type" field
$carJson = '{"type":"car","brand":"Toyota","model":"Camry","doors":4,"fuelType":"hybrid"}';
$car = $serializer->deserialize($carJson, Vehicle::class, 'json');
// $car is instance of Car class
$motorcycleJson = '{"type":"motorcycle","brand":"Harley-Davidson","model":"Sportster","engineCC":1200,"hasSidecar":false}';
$motorcycle = $serializer->deserialize($motorcycleJson, Vehicle::class, 'json');
// $motorcycle is instance of Motorcycle class
// Serialize includes the type property
$truck = new Truck();
$truck->brand = 'Volvo';
$truck->model = 'FH16';
$truck->cargoCapacity = 25.5;
$truck->axles = 3;
$json = $serializer->serialize($truck, 'json');
// Output: {"type":"truck","brand":"Volvo","model":"FH16","cargoCapacity":25.5,"axles":3}
```
## Context Options
The serializer supports numerous context options for fine-tuning serialization behavior including circular reference handling, null value skipping, and type enforcement control.
```php
name = 'Engineering';
$member = new Member();
$member->name = 'Alice';
$member->team = $team;
$team->members = [$member];
$json = $serializer->serialize($team, 'json', [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) {
return $object->name; // Return name instead of full object
}
]);
// Prevents infinite loop by returning name for circular refs
// Skip null values
class Profile
{
public string $name;
public ?string $bio = null;
public ?string $website = null;
}
$profile = new Profile();
$profile->name = 'John';
$json = $serializer->serialize($profile, 'json', [
AbstractObjectNormalizer::SKIP_NULL_VALUES => true
]);
// Output: {"name":"John"} - null values omitted
// Ignored attributes
$json = $serializer->serialize($profile, 'json', [
AbstractNormalizer::IGNORED_ATTRIBUTES => ['bio', 'website']
]);
// Output: {"name":"John"} - specified attributes excluded
// Populate existing object during deserialization
$existingProfile = new Profile();
$existingProfile->name = 'Existing';
$existingProfile->bio = 'Original bio';
$data = '{"name":"Updated","website":"https://example.com"}';
$updatedProfile = $serializer->deserialize($data, Profile::class, 'json', [
AbstractNormalizer::OBJECT_TO_POPULATE => $existingProfile
]);
// $updatedProfile->name = "Updated"
// $updatedProfile->bio = "Original bio" (preserved)
// $updatedProfile->website = "https://example.com"
```
## Callbacks
The callbacks context option allows transforming attribute values during serialization or deserialization with custom functions.
```php
id = 1001;
$order->amount = 99.99;
$order->currency = 'USD';
$order->createdAt = new \DateTime('2024-03-15 10:30:00');
$order->status = 'pending';
// Apply callbacks during serialization
$json = $serializer->serialize($order, 'json', [
AbstractNormalizer::CALLBACKS => [
// Format amount with currency symbol
'amount' => function ($value, $object, $attribute, $format, $context) {
return number_format($value, 2) . ' ' . $object->currency;
},
// Uppercase status
'status' => function ($value) {
return strtoupper($value);
},
// Custom date format
'createdAt' => function ($value) {
return $value->format('M j, Y g:i A');
},
]
]);
// Output: {"id":1001,"amount":"99.99 USD","currency":"USD","createdAt":"Mar 15, 2024 10:30 AM","status":"PENDING"}
```
## Error Handling with Collected Denormalization Errors
The serializer can collect multiple denormalization errors instead of failing on the first one, useful for validating incoming data.
```php
deserialize($invalidData, Registration::class, 'json', [
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true
]);
} catch (PartialDenormalizationException $e) {
$errors = $e->getErrors();
foreach ($errors as $error) {
echo sprintf(
"Error at '%s': %s\n",
$error->getPath(),
$error->getMessage()
);
}
// Output:
// Error at 'username': The type of the "username" attribute for class "Registration" must be string...
// Error at 'age': The type of the "age" attribute for class "Registration" must be int...
// You can still access the partially denormalized object
$partialObject = $e->getData();
}
```
The Symfony Serializer component is essential for building APIs, data import/export features, and any application requiring data format transformations. Its primary use cases include converting PHP objects to JSON for REST APIs, parsing XML from external services, generating CSV reports, and handling complex object graphs with nested relationships and circular references.
Integration typically involves configuring the serializer with appropriate normalizers (ObjectNormalizer, DateTimeNormalizer, ArrayDenormalizer) and encoders (JsonEncoder, XmlEncoder, CsvEncoder) based on required formats. In Symfony applications, the serializer is available as a service with automatic configuration; standalone usage requires manual instantiation with desired components. The component's attribute-based configuration (#[Groups], #[SerializedName], #[MaxDepth], #[Ignore]) provides declarative control over serialization behavior, while context options offer runtime flexibility for different serialization scenarios.