Attribute-driven object↔JSON/XML serializer for the MonkeysLegion v2 framework.
PHP 8.4 property hooks • readonly DTOs • backed enums • typed collections • serialization groups • naming strategies • polymorphic discriminators • PHPStan Level 9
composer require monkeyscloud/monkeyslegion-serializerRequirements: PHP ≥ 8.4 · ext-json · ext-simplexml · ext-dom
use MonkeysLegion\Serializer\Serializer;
use MonkeysLegion\Serializer\Attribute\{Serializable, SerializedName, Ignore, Groups, Type, DateFormat};
#[Serializable]
class User
{
public function __construct(
public readonly int $id,
public readonly string $name,
#[SerializedName('email_address')]
public readonly string $email,
#[Ignore]
public readonly string $passwordHash,
#[Groups(['admin'])]
public readonly string $role = 'user',
#[DateFormat('Y-m-d')]
public readonly \DateTimeImmutable $createdAt = new \DateTimeImmutable(),
) {}
}
$serializer = Serializer::create();
// Serialize to JSON
$json = $serializer->toJson(new User(
id: 1,
name: 'Jorge',
email: 'jorge@monkeys.cloud',
passwordHash: 'hashed',
role: 'admin',
));
// {"id":1,"name":"Jorge","email_address":"jorge@monkeys.cloud","role":"admin","created_at":"2026-05-17"}
// Deserialize from JSON
$user = $serializer->fromJson($json, User::class);
echo $user->name; // Jorge| Attribute | Target | Description |
|---|---|---|
#[Serializable] |
Class | Mark class as serializable (optional groups, xmlRoot) |
#[SerializedName('x')] |
Property | Override the output key name |
#[Ignore] |
Property | Exclude from serialization & deserialization |
#[Groups(['api'])] |
Property | Include only when group is active |
#[Type(Item::class)] |
Property | Hint collection item type |
#[DateFormat('Y-m-d')] |
Property | Custom date format |
#[MaxDepth(2)] |
Property | Limit nesting depth for this property |
#[Accessor(getter:, setter:)] |
Property | Custom getter/setter for private properties |
#[Transform(serialize:, deserialize:)] |
Property | Custom value transform callbacks |
#[PreSerialize] |
Method | Hook: runs before serialization |
#[PostDeserialize] |
Method | Hook: runs after deserialization |
#[Discriminator] |
Class | Polymorphic type mapping |
#[XmlRoot('user')] |
Class | XML root element name |
#[XmlElement('item')] |
Property | XML child element name |
#[XmlAttribute] |
Property | Serialize as XML attribute |
use MonkeysLegion\Serializer\Attribute\{PreSerialize, PostDeserialize};
class Order
{
public int $id;
public float $subtotal;
public float $tax;
public float $total = 0;
#[PreSerialize]
public function computeTotal(): void
{
$this->total = $this->subtotal + $this->tax;
}
#[PostDeserialize]
public function onLoaded(): void
{
$this->total = $this->subtotal + $this->tax;
}
}use MonkeysLegion\Serializer\Attribute\Transform;
class Contact
{
#[Transform(serialize: 'strtolower', deserialize: 'strtolower')]
public string $email;
#[Transform(serialize: 'trim')]
public string $name;
}use MonkeysLegion\Serializer\Attribute\Accessor;
class User
{
#[Accessor(getter: 'getEmail', setter: 'setEmail')]
private string $email;
public function getEmail(): string { return $this->email; }
public function setEmail(string $v): void { $this->email = strtolower($v); }
}use MonkeysLegion\Serializer\Attribute\MaxDepth;
class User
{
public int $id;
#[MaxDepth(1)]
public ?Company $company; // Only serialized at top-level, not nested
}use MonkeysLegion\Serializer\Exception\{
SerializerException,
SerializationException,
DeserializationException,
};
try {
$user = $serializer->fromJson($invalidJson, User::class);
} catch (DeserializationException $e) {
// Handle deserialization-specific errors
} catch (SerializerException $e) {
// Catch all serializer errors
}use MonkeysLegion\Serializer\DTO\SerializationContext;
// Only include 'api' group fields
$json = $serializer->serialize($user, 'json', new SerializationContext(
groups: ['api'],
));use MonkeysLegion\Serializer\Enum\NamingStrategy;
// camelCase output
$serializer = Serializer::create(NamingStrategy::CamelCase);
// snake_case output (default)
$serializer = Serializer::create(NamingStrategy::SnakeCase);
// kebab-case output
$serializer = Serializer::create(NamingStrategy::KebabCase);use MonkeysLegion\Serializer\Attribute\{XmlRoot, XmlElement, XmlAttribute};
#[XmlRoot('order')]
class Order
{
#[XmlAttribute]
public int $id;
#[XmlElement('line_item')]
public array $items;
}
$xml = $serializer->toXml($order);enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
}
class Account
{
public function __construct(
public readonly int $id,
public readonly Status $status,
) {}
}
// Enums serialize to their scalar value automatically
$json = $serializer->toJson(new Account(1, Status::Active));
// {"id":1,"status":"active"}
// And deserialize back
$account = $serializer->fromJson($json, Account::class);
$account->status === Status::Active; // trueuse MonkeysLegion\Serializer\Attribute\Discriminator;
#[Discriminator(field: 'type', map: [
'cat' => Cat::class,
'dog' => Dog::class,
])]
abstract class Animal
{
public string $name;
}
class Cat extends Animal { public int $lives = 9; }
class Dog extends Animal { public string $breed; }
$json = '{"type":"cat","name":"Whiskers","lives":7}';
$animal = $serializer->fromJson($json, Animal::class);
// Returns Cat instanceuse MonkeysLegion\Serializer\Attribute\Type;
class Order
{
public int $id;
#[Type('list<OrderItem>')]
public array $items = [];
}
// Serialize/deserialize lists
$json = $serializer->serializeList($orders);
$orders = $serializer->deserializeList($json, Order::class);| Option | Type | Default | Description |
|---|---|---|---|
groups |
list<string> |
[] |
Active groups (empty = all) |
maxDepth |
int |
10 |
Max object nesting depth |
dateFormat |
string |
ISO 8601 | Default date format |
serializeNulls |
bool |
false |
Include null values |
| Option | Type | Default | Description |
|---|---|---|---|
groups |
list<string> |
[] |
Active groups (empty = all) |
ignoreUnknown |
bool |
true |
Skip unknown keys |
dateFormat |
string |
ISO 8601 | Expected date format |
strictTypes |
bool |
false |
Enforce strict coercion |
use MonkeysLegion\Serializer\Provider\SerializerProvider;
// Register in your container
$serializer = SerializerProvider::register($config['serializer'] ?? []);php ml serializer:installserializer {
default_format = json
naming_strategy = snake_case
serialize_nulls = false
date_format = "Y-m-d\TH:i:sP"
max_depth = 10
json {
pretty_print = false
}
xml {
version = "1.0"
encoding = "UTF-8"
root_tag = "root"
}
}
MIT © 2026 MonkeysCloud Team