Skip to content

Inconsistent behaviour when deserializing json with snake_case properties #61054

Open
@MariusJam

Description

@MariusJam

Symfony version(s) affected

7.3.1

Description

Hello,

When deserializing a json document with properties written in snake_case, the behaviour seems inconsistent depending on the property visibility and the use of getters/setters or hooks.

  • public properties are not deserialized
  • private properties with getters / setters are deserialized
  • private properties with hook are not deserialized

How to reproduce

Given this class which is the deserialization target:

namespace App\Model;

class TestDeserialize {
    public string $publicKey = "";
    private string $privateKey = "";

    private string $hookedKey = "" {
        get => $this->hookedKey;
        set => $this->hookedKey = $value;
    }

    public function getPrivateKey(): string
    {
        return $this->privateKey;
    }

    public function setPrivateKey(string $privateKey): void
    {
        $this->privateKey = $privateKey;
    }
}

Below is a command which reproduce the issue:

<?php

namespace App\Command;

use App\Model\TestDeserialize;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Serializer\SerializerInterface;

#[AsCommand(
    name: 'test:deserialize',
    description: 'Add a short description for your command',
)]
class TestSerialCommand extends Command
{
    private const string JSON =
        <<<JSON
        {
          "public_key": "foo",
          "private_key": "bar",
          "hooked_key": "fuz" 
        }
        JSON;


    public function __construct(private readonly SerializerInterface $serializer)
    {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        dump($this->serializer->deserialize(self::JSON, TestDeserialize::class, 'json'));
        return Command::SUCCESS;
    }
}

In the dump, we can see the inconsistency of the result:

App\Model\TestDeserialize^ {#1016
  +publicKey: ""
  -privateKey: "bar"
  -hookedKey: ""
}

Possible Solution

No response

Additional Context

I have been able to track down the issue to \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor::isWritable() (which is called by \Symfony\Component\Serializer\Normalizer\ObjectNormalizer::isAllowedAttribute()). In the following lines, a reflectionMethod will be found for a private property with a setter method, whereas it will return null in other cases.

// First test with the camelized property name
        [$reflectionMethod] = $this->getMutatorMethod($class, $this->camelize($property));
        if (null !== $reflectionMethod) {
            return true;
        }

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