Search code examples
symfonyjson-deserializationjmsserializerbundlejms-serializer

How to make JMS Serializer throw an exception on deserializing JSON instead of coercing types?


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?

Update 2016-02-27

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?


Solution

  • 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.