4

I have a OrderDto class with a nested PointDto class (array of points):

class OrderDto
{
    /**
     * @var PointDto[]
     * @Assert\All({
     *     @Assert\Type("App\Dto\PointDto")
     * })
     * @Assert\Valid()
     */
    private array $points;

    // getters, setters
}

The PointDto class also uses validator constraints:

class PointDto
{
    /**
     * @Assert\NotBlank()
     */
    private string $address;

    // getters, setters
}

My controller:

/**
  * @Rest\Post("/order/calc")
  * @ParamConverter("orderDto", converter="fos_rest.request_body")
  */
public function calcOrder(OrderDto $orderDto, ConstraintViolationListInterface $validationErrors)
{
    if (count($validationErrors) > 0)
        return $this->json($validationErrors, Response::HTTP_BAD_REQUEST);
    return ApiResponseUtil::okData(['sum' => 0]);
}

But when is send request with nested dto object, like this:

{
    "points": [
        {
            "address": "",
            "person": {
                "name": "",
                "phone": ""
            }
        }
    ]
}

The validator cannot determine the type, error:

{
  "error": "points[0]: This value should be of type App\\Dto\\PointDto.",
  "violations": [
    {
      "property": "points[0]",
      "message": "This value should be of type App\\Dto\\PointDto."
    }
  ]
}

Is there any way to deserialize nested object?

inkrot
  • 448
  • 5
  • 12
  • Your issue seems to be with how Symfony Serializer resolves your list of Points. It should be able to resolve this when you use `PointDto[]` (as you do). Can you make sure that the ArrayDenormalizer is registered in the `serializer`-service? I think the easiest way to find out is to inject the SerializerInterface into the controller and then dump it. – dbrumann Feb 26 '20 at 10:23
  • @dbrumann, sorry for the long answer I looked in the dump serializer service, it looks like this: ^ Symfony\Component\Serializer\Serializer {#447 ▼ #encoder: Symfony\Component\Serializer\Encoder\ChainEncoder {#425 ▶} #decoder: Symfony\Component\Serializer\Encoder\ChainDecoder {#376 ▶} #normalizers: array:15 [▼ 0 => App\Serializer\Normalizer\ConstraintViolationListNormalizer {#448} ... 13 => Symfony\Component\Serializer\Normalizer\ArrayDenormalizer {#428 ▼ -serializer: Symfony\Component\Serializer\Serializer {#447} } ... ] – inkrot Mar 10 '20 at 12:15
  • It looks like the ArrayDenormalizer is present at least. That should be able to give you an array of objects. I don't have any other idea what could be wrong. A minimal example project would help. I try and see if I can spend an hour looking into this later today. – dbrumann Mar 10 '20 at 15:24
  • I have created a minimal example with a test case and it works fine. Both tests pass: https://gist.github.com/dbrumann/e379ef8c1511f3d36822cf14c3855857 Can you maybe try removing the Validation-Constraints and check if deserialization works. Maybe these annotations conflict with how the Serializer infers the types. – dbrumann Mar 10 '20 at 20:38
  • @dbrumann, when collecting an example for you, fos_rest.request_body worked like magic, is such an error possible due to an uncleaned cache? – inkrot Mar 10 '20 at 22:38
  • 1
    I found that my error occurs when absent: phpdocumentor/reflection-docblock as soon as I installed then everything worked – inkrot Mar 10 '20 at 22:44
  • Yes, in my test I had an issue with the assertions in the docbkock as well, because it could not determine what to do with them. That's why I figured that could be the cause. I know that you can configure the doctrine annotations to ignore certain annotations, but I did not want to go down that path without being sure. Good to hear it's solved for you. – dbrumann Mar 11 '20 at 09:27

1 Answers1

0

I faced with the similar problem.

I had to use @var annotation for a field, iterable as it's type and ArrayCollection as initialized value.

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

class OrderDto
{
    /**
     * @var PointDto[] - this annotation is super important
     *
     * @ODM\Field
     * @Assert\All({
     *     @Assert\Type("App\Dto\PointDto")
     * })
     * @Assert\Valid()
     */
    private iterable $points;

    public function __construct()
    {
        $this->points = new ArrayCollection();
    }

    public function setPoints(iterable $points) // Don't use Collection type here, it will lead to a type error
    {
        $this->points = $points;
    }
}
Serhii Smirnov
  • 1,338
  • 1
  • 16
  • 24