serializer icon indicating copy to clipboard operation
serializer copied to clipboard

Detect extra fields in deserialization

Open enumag opened this issue 8 years ago • 19 comments

I need to deserialize a json into a class. It works fine but I noticed that when I add a field to the json that does not exist on the class it's silently ignored. I need to know about it somehow to throw a correct exception - such data are considered as invalid for my use case.

enumag avatar Nov 01 '17 13:11 enumag

Hi, thanks for the request. Currently there is nothing in the core that allows something similar. Maybe a pre/post serialization listener can do the work for you here...

goetas avatar Nov 01 '17 13:11 goetas

That's quite surprising. This seems like a very basic feature that everybody should need when deserializing to a class.

How do I get a list of fields that deserializer is able to fill in the handler?

enumag avatar Nov 01 '17 14:11 enumag

PropertyMetadata gives you the type of data you are going to unserialize. from it you can infer what you need

goetas avatar Nov 01 '17 14:11 goetas

And how do I create a handler that is used always? When I remove the type key from getSubscribingMethods I get Each method returned from getSubscribingMethods of service "MyHandler" must have a "type", and "format" attribute.

enumag avatar Nov 01 '17 14:11 enumag

:( handlers are always per "type"...

goetas avatar Nov 01 '17 14:11 goetas

Then it's kind of unsolveable with a handler... any tip where in the code of Serializer this should be handled + how the user should configure that they want to use this behavior? I want to send a PR but don't know where to begin.

enumag avatar Nov 01 '17 14:11 enumag

A nice thing to do can be to allow listeners to be triggered on any type... and that should be something here

goetas avatar Nov 01 '17 14:11 goetas

According to documentation event subscribers can work on any type already.

enumag avatar Nov 01 '17 15:11 enumag

what about your message from one hour ago?

And how do I create a handler that is used always? When I remove the type key from getSubscribingMethods I get Each method returned from getSubscribingMethods of service "MyHandler" must have a "type", and "format" attribute.

goetas avatar Nov 01 '17 15:11 goetas

The link you sent is not about handlers but about Events, right? In event subscribers class is optional. Should I use events instead of handler then?

enumag avatar Nov 01 '17 15:11 enumag

My bad, on my first message I wanted to write :

Hi, thanks for the request. Currently there is nothing in the core that allows something similar. Maybe a pre/post serialization listener can do the work for you here...

I've edited the comment for clarity. So you have to implement and event listener.

goetas avatar Nov 01 '17 15:11 goetas

Ok, I'll try it. Thanks.

enumag avatar Nov 01 '17 15:11 enumag

I think it will work... This is what I have so far. What do you think?

<?php declare(strict_types = 1);

namespace App\Web;

use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;

class MyEventSubscriber implements EventSubscriberInterface
{
	public static function getSubscribedEvents(): array
	{
		return [
			[
				'event' => 'serializer.pre_deserialize',
				'method' => 'onPreDeserialize',
			],
		];
	}

	public function onPreDeserialize(PreDeserializeEvent $event)
	{
		$metadata = $event->getContext()->getMetadataFactory()->getMetadataForClass($event->getType()['name']);
		$attributes = array_keys($event->getData());
		$properties = array_keys($metadata->propertyMetadata);
		$extraAttributes = array_diff($attributes, $properties);

		if ($extraAttributes) {
			throw new \Exception();
		}
	}
}

enumag avatar Nov 01 '17 16:11 enumag

array_keys($event->getData()); will work only for json/yml but the general idea is correct

goetas avatar Nov 02 '17 07:11 goetas

Ran into this today. I expected invalid properties to produce an error when deserialized. I see from this issue that's not the expected behavior but I agree with the OP that it isn't unreasonable to expect. Perhaps a configuration setting to enable this like Symfony's serializer does?

See https://symfony.com/doc/current/components/serializer.html#deserializing-an-object.

pdugas avatar Sep 11 '18 14:09 pdugas

I ran into this issue today as well, I agree that allow_extra_attributes option in the context would be awesome.

douglasjam avatar Jun 26 '19 13:06 douglasjam

The same problem. At the development stage, I would like to receive exceptions in case of differences in the classes and structure of the json.

4n70w4 avatar Feb 02 '20 18:02 4n70w4

@enumag your solution does not work if @Discriminator is used. https://jmsyst.com/libs/serializer/master/reference/annotations#discriminator

Does anyone have a workaround?

4n70w4 avatar Feb 02 '20 18:02 4n70w4

Workaround for @Discriminator

<?php declare(strict_types = 1);


use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;

class JmsSerializerEventSubscriber implements EventSubscriberInterface {



    public static function getSubscribedEvents(): array {
        return [
            [
                'event' => 'serializer.pre_deserialize',
                'method' => 'onPreDeserialize',
            ],
        ];
    }



    public function onPreDeserialize(PreDeserializeEvent $event) {
        $metadata = $event->getContext()->getMetadataFactory()->getMetadataForClass($event->getType()['name']);

        if($metadata->discriminatorFieldName) {
            $event->getData()[$metadata->discriminatorFieldName];
            $class = $metadata->discriminatorMap[$event->getData()[$metadata->discriminatorFieldName]];
            $metadata = $event->getContext()->getMetadataFactory()->getMetadataForClass($class);
        }

        $attributes = array_keys($event->getData());
        $properties = array_keys($metadata->propertyMetadata);
        $extraAttributes = array_diff($attributes, $properties);

        if($extraAttributes) {
            throw new \Exception(json_encode($extraAttributes) );
        }

    }



}

Usage:

    /**
     * @param string $data
     *
     * @return Response
     */
    protected function deserialize(string $data): Response {
        AnnotationRegistry::registerLoader('class_exists');

        // $serializer = SerializerBuilder::create()->build();
        $builder = SerializerBuilder::create();
        $builder->setPropertyNamingStrategy(new IdenticalPropertyNamingStrategy());
        $builder->configureListeners(function(EventDispatcherInterface $EventDispatcher) {
            $EventDispatcher->addSubscriber(new JmsSerializerEventSubscriber() );
        });

        $serializer = $builder->build();

        $object = $serializer->deserialize($data, Response::class, 'json');
        return $object;
    }

4n70w4 avatar Feb 02 '20 19:02 4n70w4