I'm trying to write a REST API which consumes a JSON from a PUT request in Symfony2. Deserializing the JSON to an entity sort of works – but the JMS Serializer seems to coerce types from the JSON instead of throwing an exception if the type of a property in the JSON does not match the entity’s corresponding property.
For example …
{ "id" : "123" }
… will result in …
int(123)
… if the property id
is defined as an integer in the entity.
But I would like JMS Serializer to throw an exception instead. Does anyone know how to achieve this?
One problem with JMS Serializer’s type handling I found is this:
{ "id" : "n123" }
will result in …
int(0)
which is totally undesired.
Can someone please point me into the right direction?
Building on top of reieRMeister's answer, when it comes to JSON deserialisation, I don't think it's wise to coerce primitive types by default.
reieRMaster's answer is good when a distinction needs to be made between "strict" and "loose" type by explicitly setting the type to strict_integer
for each property. But this becomes somewhat tedious if you wish to use the built in feature where the type is determined using doctrine metadata.
You can override this default behaviour for all the primitive types by overriding the jms_serializer.json_deserialization_visitor.class
with one of your own classes like below:
<?php
namespace MyBundle\Serializer\Visitor;
use JMS\Serializer\JsonDeserializationVisitor;
use JMS\Serializer\Context;
class JsonNativeDeserializationVisitor extends JsonDeserializationVisitor
{
public function visitString($data, array $type, Context $context)
{
return $data;
}
public function visitBoolean($data, array $type, Context $context)
{
return $data;
}
public function visitInteger($data, array $type, Context $context)
{
return $data;
}
public function visitDouble($data, array $type, Context $context)
{
return $data;
}
}
and in services.xml (or services.yml) override jms_serializer.json_deserialization_visitor.class
<parameters>
<parameter key="jms_serializer.json_deserialization_visitor.class">MyBundle\Serializer\Visitor\JsonNativeDeserializationVisitor</parameter>
</parameters>
Gotcha! Make sure that your bundle is registered AFTER JMSSerializer bundle, or the above will not work. How to do that is described here
After getting help over at Github I want to share an answer on my own question.
The key to a solution is using a custom handler which implements the JMS\Serializer\Handler\SubscribingHandlerInterface
(e.g. a StrictIntegerHandler
).
<?php
namespace MyBundle\Serializer;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonDeserializationVisitor;
use JMS\Serializer\JsonSerializationVisitor;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class StrictIntegerHandler implements SubscribingHandlerInterface
{
public static function getSubscribingMethods()
{
return [
[
'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
'format' => 'json',
'type' => 'strict_integer',
'method' => 'deserializeStrictIntegerFromJSON',
],
[
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'strict_integer',
'method' => 'serializeStrictIntegerToJSON',
],
];
}
public function deserializeStrictIntegerFromJSON(JsonDeserializationVisitor $visitor, $data, array $type)
{
return $data;
}
public function serializeStrictIntegerToJSON(JsonSerializationVisitor $visitor, $data, array $type, Context $context)
{
return $visitor->visitInteger($data, $type, $context);
}
}
You will then need to define the serializer as a service:
services:
mybundle.serializer.strictinteger:
class: MyBundle\Serializer\StrictIntegerHandler
tags:
- { name: jms_serializer.subscribing_handler }
Then you will be able to use the type strict_integer
:
MyBundle\Entity\MyEntity:
exclusion_policy: ALL
properties:
id:
expose: true
type: strict_integer
Deserializing in the controller then works as usual.
Bonus: Using a type validator now finally makes sense:
MyBundle\Entity\MyEntity:
properties:
id:
- Type:
type: integer
message: id {{ value }} is not an integer.
I hope this helps those with the same problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With