Skip to content

[JsonStreamer] Nullable \DateTimeInterface is not decoded #63268

@aleho

Description

@aleho

Symfony version(s) affected

7.4

Description

Trying to decode a valid JSON object into a DTO fails for nullable \DateTimeInterface properties (also concrete implementations) with:

Unexpected "string" value for "DateTimeInterface|null".

The generated code seems to check for array data:

$providers['DateTimeInterface|null'] = static function ($stream, $offset, $length) use ($options, $valueTransformers, $instantiator, &$providers) {
    $data = \Symfony\Component\JsonStreamer\Read\Decoder::decodeStream($stream, $offset, $length);
    if (\is_array($data)) {
        return $providers['DateTimeInterface']($data);
    }
    if (null === $data) {
        return null;
    }
    throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'DateTimeInterface|null'));
};
return $providers['App\TestDto']($stream, 0, null);

How to reproduce

<?php

declare(strict_types = 1);

namespace App;

use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;

#[JsonStreamable]
class TestDto
{
    public int $id;
    public \DateTimeInterface $date;
    public ?\DateTimeInterface $nullable = null;
}
<?php

declare(strict_types = 1);

namespace App;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\JsonStreamer\StreamReaderInterface;
use Symfony\Component\TypeInfo\Type;
use Symfony\Contracts\HttpClient\HttpClientInterface;

#[AsCommand(
    name       : 'app:test',
    description: 'Test command',
)]
readonly class TestCommand
{
    public function __construct(
        private HttpClientInterface $httpClient,
        private StreamReaderInterface $jsonStreamReader,
    ) {
    }

    public function __invoke(): int
    {
        $response = $this->httpClient->request('GET', 'https://fify.website/__object.json');

        /** @var TestDto $dto */
        $dto = $this->jsonStreamReader->read($response->toStream(), Type::object(TestDto::class));

        dump($dto->id);
        dump($dto->date);
        dump($dto->nullable);

        return Command::SUCCESS;
    }
}
{
  "id": 100,
  "date": "2026-02-03T08:38:26+01:00",
  "nullable": "2026-02-03T08:38:26+00:00"
}

Possible Solution

Maybe this check isn't working correctly?

if ($type instanceof ObjectType) {
return "\\is_array($accessor)";
}

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