Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Serializer] Deserialize union type of BackedEnum does not work  #47797

Copy link
Copy link
Closed
@Gwemox

Description

@Gwemox
Issue body actions

Symfony version(s) affected

5.4.12 and newer
6.x

Description

Hello,
It becomes impossible to deserialize a union type that contains a BackedEnum, because InvalidArgumentException is not caught.

It seems that a bug is introduced with the commit symfony/serializer@3fc9afe.

How to reproduce

composer.json

{
    "require": {
        "symfony/serializer": "^5.4",
        "symfony/property-access": "^5.4"
    }
}

index.php

<?php
require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

Enum SubEnumA : string {
    case Toto = 'toto';
    case Toto2 = 'toto2';
}

Enum SubEnumB : string {
    case Tata = 'tata';
    case Tata2 = 'tata2';
}

class A {
    public SubEnumA|SubEnumB $sub;

    public function __construct(SubEnumA|SubEnumB $sub)
    {
        $this->sub = $sub;
    }
}

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader());
$encoders = [new JsonEncoder()];
$reflectionExtractor = new ReflectionExtractor();
$propertyInfoExtractor = new PropertyInfoExtractor(
    [$reflectionExtractor],
    [$reflectionExtractor],
    [],
    [$reflectionExtractor],
    [$reflectionExtractor]
);
$normalizers = [
    new BackedEnumNormalizer(),
    new ObjectNormalizer($classMetadataFactory, null, null, $propertyInfoExtractor),
];

$serializer = new Serializer($normalizers, $encoders);

$a = new A(SubEnumB::Tata);

$data = $serializer->serialize($a, 'json');
$a = $serializer->deserialize($data, A::class, 'json', [
    AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
var_dump($a);

Deserialize A does not work
Expected :

object(A)#35 (1) {
  ["sub"]=>
  enum(SubEnumB::Tata)
}

Result :

PHP Fatal error:  Uncaught Symfony\Component\Serializer\Exception\InvalidArgumentException: The data must belong to a backed enumeration of type SubEnumA in /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/BackedEnumNormalizer.php:67
Stack trace:
#0 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Serializer.php(238): Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer->denormalize()
#1 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(567): Symfony\Component\Serializer\Serializer->denormalize()
#2 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(635): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize()
#3 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractNormalizer.php(383): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalizeParameter()
#4 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(281): Symfony\Component\Serializer\Normalizer\AbstractNormalizer->instantiateObject()
#5 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(363): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->instantiateObject()
#6 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Serializer.php(238): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize()
#7 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Serializer.php(151): Symfony\Component\Serializer\Serializer->denormalize()
#8 /home/thibault/workspace/mpp-api/issue_XXX.php(54): Symfony\Component\Serializer\Serializer->deserialize()
#9 {main}
  thrown in /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/BackedEnumNormalizer.php on line 67

Possible Solution

In method denormalize on Symfony\Component\Serializer\NormalizerBackedEnumNormalizer change InvalidArgumentException by NotNormalizableValueException

    /**
     * {@inheritdoc}
     *
     * @throws NotNormalizableValueException
     */
    public function denormalize($data, string $type, string $format = null, array $context = [])
    {
        if (!is_subclass_of($type, \BackedEnum::class)) {
            throw new InvalidArgumentException('The data must belong to a backed enumeration.');
        }

        if (!\is_int($data) && !\is_string($data)) {
            throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
        }

        try {
            return $type::from($data);
        } catch (\ValueError $e) {
            throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type);
        }
    }

From:

        try {
            return $type::from($data);
        } catch (\ValueError $e) {
            throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type);
        }

To:

        try {
            return $type::from($data);
        } catch (\ValueError $e) {
            throw new NotNormalizableValueException('The data must belong to a backed enumeration of type '.$type);
        }

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

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.