mhujer/jms-serializer-uuid-bundle
Symfony bundle that integrates ramsey/uuid with JMS Serializer, registering handlers to serialize and deserialize UUID objects—including uuid-doctrine types—so your JSON APIs handle UUIDs cleanly without custom visitors.
This bundle is a thin Symfony bridge: it registers one JMS Serializer subscribing handler that converts Ramsey\Uuid\UuidInterface values to/from strings. There is no bundle configuration to tune — enabling the bundle is the integration.
1. Install (legacy stack only). The last release is from 2016 and pins Symfony 2.7/3.x, jms/serializer 0.16/1.x, and ramsey/uuid 2.x/3.x:
composer require mhujer/jms-serializer-uuid-bundle
On Symfony 4+ or ramsey/uuid 4+, expect composer why-not conflicts. In that case skip the package and copy the handler (see Gotchas).
2. Register the bundle in app/AppKernel.php (the repo predates Flex recipes, so there is no config/bundles.php auto-registration):
public function registerBundles()
{
return [
// ...
new MhujerJmsSerializerUuidBundle(),
];
}
3. Verify the handler is tagged as a JMS handler, not merely registered as a service:
bin/console debug:container --tag=jms_serializer.subscribing_handler
4. First use case — round-trip a UUID property through JSON:
use JMS\Serializer\Annotation as Serializer;
use Ramsey\Uuid\UuidInterface;
class User
{
/**
* @Serializer\Type("Ramsey\Uuid\UuidInterface")
*/
private $id;
public function __construct(UuidInterface $id)
{
$this->id = $id;
}
public function getId(): UuidInterface
{
return $this->id;
}
}
$json = $serializer->serialize($user, 'json');
// {"id":"9f1c0f2e-4a8b-4c1e-9f7d-2b6a1c8e4d33"}
$user = $serializer->deserialize($json, User::class, 'json');
$user->getId(); // Ramsey\Uuid\Uuid instance
Where to look first: the handler's getSubscribingMethods() in src/ — it defines the exact type string and formats that work. Then Resources/config/services.* for the jms_serializer.subscribing_handler tag, and composer.json for hard version ceilings.
Annotate with the exact subscribed type. The property annotation's type string must match the handler registration exactly. Use @Serializer\Type("Ramsey\Uuid\UuidInterface"); this is the package's entire public API. Deserialization produces the concrete Ramsey\Uuid\Uuid class.
Use input DTOs for request bodies. JMS 1.x does not reliably hydrate constructor arguments, so accept UUIDs on a DTO and map into your immutable entity:
class CreateUserInput
{
/** @Serializer\Type("Ramsey\Uuid\UuidInterface") */
public $id;
/** @Serializer\Type("string") */
public $name;
}
$input = $serializer->deserialize($request->getContent(), CreateUserInput::class, 'json');
$user = new User($input->id, $input->name);
Keep serialization and Doctrine mapping separate. With ramsey/uuid-doctrine, Doctrine converts the DB column while this handler converts JSON. Both operate on the same UuidInterface field; neither knows about the other. Test both paths independently.
Use a virtual property for output-only string fields. If a consumer expects a plain string and you do not want the handler involved:
/**
* @Serializer\VirtualProperty
* @Serializer\SerializedName("id")
*/
public function idAsString(): string
{
return $this->id->toString();
}
For input, provide a setter that accepts a string and calls Uuid::fromString().
Reuse the handler without booting Symfony in unit tests:
$serializer = \JMS\Serializer\SerializerBuilder::create()
->configureHandlers(function (\JMS\Serializer\Handler\HandlerRegistry $registry) {
$registry->registerSubscribingHandler(new \Mhujer\JmsSerializer\Uuid\UuidSerializerHandler());
})
->build();
$this->assertSame(
$uuid->toString(),
json_decode($serializer->serialize(['id' => $uuid], 'json'), true)['id']
);
Round-trip assertions are the cheapest regression test. Serialize a known UUID, decode the JSON, assert the canonical dashed lowercase form, then deserialize and assert instanceof UuidInterface plus value equality.
Error mapping belongs in your API layer. Wrap deserialization or add an exception listener that converts invalid UUID exceptions into HTTP 400 rather than 500.
jms/serializer 0.16/1.x, ramsey/uuid 2/3 only. Do not fight composer; copy the roughly 40-line handler into App\Serializer and tag it:services:
App\Serializer\UuidHandler:
tags:
- { name: jms_serializer.subscribing_handler }
final class UuidHandler implements SubscribingHandlerInterface
{
public static function getSubscribingMethods(): array
{
$methods = [];
foreach (['json', 'xml', 'yml'] as $format) {
$methods[] = [
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => $format,
'type' => UuidInterface::class,
'method' => 'serialize',
];
$methods[] = [
'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
'format' => $format,
'type' => UuidInterface::class,
'method' => 'deserialize',
];
}
return $methods;
}
public function serialize($visitor, UuidInterface $uuid, array $type, Context $context): string
{
return $uuid->toString();
}
public function deserialize($visitor, string $data, array $type, Context $context): UuidInterface
{
return Uuid::fromString($data);
}
}
On JMS 3.x, GraphNavigator is deprecated in favour of GraphNavigatorInterface; adjust the constants when copying.
getSubscribingMethods() first when deserialization misbehaves.format keys before exposing multiple representations. The copied handler above covers JSON, XML, and YAML.null, but if the handler is invoked, a non-nullable type hint throws TypeError. Keep UUID fields nullable where the domain allows it and guard in the handler.Uuid::fromString() rejects malformed strings with an InvalidArgumentException-family exception (e.g. InvalidUuidStringException). Convert this to a 400 in your API layer; otherwise clients see a 500.fromString() accepts non-canonical forms. Undashed hex and some variants are valid input. If your API contract requires canonical dashed lowercase output, always emit toString() and normalize on input.config/bundles.php if you insist on using it, and clear the cache after registering so the container picks up the handler tag.How can I help you explore Laravel packages today?