Skip to content

[Serializer] Handling array properties during denormalization #51261

Closed
@X-Coder264

Description

@X-Coder264

Symfony version(s) affected

6.3

Description

Updating from Serializer 5.4 to 6.3 breaks our testsuite as the denormalizer which is supposed to denormalize an array property does not get executed anymore as it does implement the getSupportedTypes method and in the serializer code path that handles the getSupportedTypes logic it no longer calls the supportsDenormalization method on that denormalizer (the concrete example can be seen under the steps to reproduce). I'm not entirely familiar with the Serializer component so maybe I'm just doing something wrong.

How to reproduce

The interfaces/classes needed to reproduce this:

interface FooBaseDummy
{
}
class FooDummy implements FooBaseDummy
{
    /**
     * @var string
     */
    public $name;
}
class ArrayPropertyDummy
{
    /**
     * @var FooDummy[]
     */
    public $foo;

    public function getFoo(): array
    {
        return $this->foo;
    }
}
final class FooBaseDummyDenormalizer implements DenormalizerInterface
{
    public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
    {
        $result = [];
        foreach ($data as $foo) {
            $fooDummy = new FooDummy();
            $fooDummy->name = $foo['name'];
            $result[] = $fooDummy;
        }

        return $result;
    }

    public function supportsDenormalization(mixed $data, string $type, string $format = null)
    {
        if (mb_substr($type, -2) === '[]') {
            $className = mb_substr($type, 0, -2);
            $classImplements = class_implements($className);
            \assert(\is_array($classImplements));

            return class_exists($className) && \in_array(FooBaseDummy::class, $classImplements, true);
        }

        return false;
    }

    /**
     * @return array<string, bool>
     */
    public function getSupportedTypes(?string $format): array
    {
        return [FooBaseDummy::class.'[]' => false];
    }
}

The test that I expect to pass but currently does not with Serializer 6.3:

    public function testDeserializeOnObjectWithArrayProperty()
    {
        $serializer = new Serializer([new FooBaseDummyDenormalizer(), new ObjectNormalizer(null, null, null, new PhpDocExtractor())], [new JsonEncoder()]);

        $obj = $serializer->deserialize('{"foo":[{"name":"bar"}]}', ArrayPropertyDummy::class, 'json');
        $this->assertInstanceOf(ArrayPropertyDummy::class, $obj);
        $this->assertInstanceOf(FooDummy::class, $obj->getFoo()[0]);
    }

The test passes when using Serializer 5.4

Removing the getSupportedTypes method from the FooBaseDummyDenormalizer when using Serializer 6.3 makes the test pass again (but with a deprecation). The FooBaseDummyDenormalizer is a simplified equivalent of a class from a bundle we use in our app.
When we use Serializer 5.4 the serializer calls $normalizer->supportsDenormalization which returns true and everything works fine ->

} elseif ($normalizer->supportsDenormalization(null, $class, $format, $context)) {

When we use Serializer 6.3 it skips that code path as the denormalizer has the getSupportedTypes method so it enters this if statement which just does a continue ->
if (\in_array($supportedType, ['*', 'object'], true)
|| $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType))
) {
continue;
}

$supportedType here is Symfony\Component\Serializer\Tests\Fixtures\FooBaseDummy[], $class is Symfony\Component\Serializer\Tests\Fixtures\FooDummy[] and $genericType is *

Possible Solution

No response

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions