Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 864
reieRMeister Avatar asked Feb 19 '16 10:02

reieRMeister


2 Answers

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

like image 20
dnshio Avatar answered Sep 21 '22 00:09

dnshio


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.

like image 96
reieRMeister Avatar answered Sep 21 '22 00:09

reieRMeister