Having PHP, Symfony 4.4, JMS Serializer and json payload (request body) as such:
{
"quantity": 1,
"product": {
"sku": "bla"
},
"myId": {
"id": "0010N00005GcOhhQAF"
}
}
This payload is send to my endpoint and everything is correctly deserialized into correct resulting CustomRequest
object - also nested objects like Product
and MyId
is correctly created. In the case of the Product
object it is ok, because Product
have complex structure with multiple attributes.
But what I would like to achieve is to make the input of myId
easier. I would like to have it instead of:
"myId": {
"id": "0010N00005GcOhhQAF"
}
having simple this:
"myId": "0010N00005GcOhhQAF"
You may be asking why do I have class for simple id. It is not simple id, it have some special validation and bussiness logic inside and is used through out all application, so for validation purposes its better to have an object for it.
So lets say, I want my deserializer to automatically take that simple id string into the constructor of the class MyId, and return the object. MyId class is simple class like:
class MyId
{
/**
* @AppAssert\MyId()
*/
private ?string $value = null;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString()
{
return $this->value;
}
}
FYI: I tried this annotation in the resulting CustomRequest
object, but it was not working
/**
* @Serializer\Type("App\Model\MyId")
*/
private ?MyId $myId = null;
EDIT: Another important part: this is how the endpoint automatically transforms request body into the CustomRequest
object. Here you can see, that I am using ParamConverter
for it.
/**
* @Rest\Post("/product")
* @ParamConverter("customRequest", options={
* "validator"={"groups"={"Default","product"="create"}},
* })
*/
public function postCreateProductAction(CustomRequest $customRequest) {
// ...
}
The question is: How to use JMS Serializer with Symfony to make it work. To take simple one string pass it atomatically to constructor and make and object from it. Is this even possible? Thanks
I made it working by using JMS\Serializer\Handler\SubscribingHandlerInterface
. With this approach you can just simply add callbacks that kicks in during serialization/deserialization process. See code below that shows exact solution for MyId
object.
More info about this technique here: https://jmsyst.com/libs/serializer/master/handlers
namespace App\Handlers;
use App\Model\MyId;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonDeserializationVisitor;
use JMS\Serializer\JsonSerializationVisitor;
class MyIdHandler implements SubscribingHandlerInterface
{
/**
* @return array<int, array<string, int|string>>
*/
public static function getSubscribingMethods(): array
{
return [
[
'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => MyId::class,
'method' => 'serializeMyIdToJson',
],
[
'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
'format' => 'json',
'type' => MyId::class,
'method' => 'deserializeMyIdFromJson',
],
];
}
/**
* @param array<mixed> $type
*/
public function serializeMyIdToJson(JsonSerializationVisitor $visitor, ?MyId $myId, array $type, Context $context): string
{
if ($myId === null) {
return '';
}
return (string)$myId;
}
/**
* @param array<mixed> $type
*/
public function deserializeMyIdFromJson(JsonDeserializationVisitor $visitor, string $myId, array $type, Context $context): ?MyId
{
if (empty($myId)) {
return null;
}
return new MyId($myId);
}
}