Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Serializer] Preserve array keys while normalize/denormalize variadic parameters #49616

Closed
melya opened this issue Mar 6, 2023 · 0 comments
Closed

Comments

@melya
Copy link
Contributor

melya commented Mar 6, 2023

Description

Hi symfony!

Currently I'm in a need in preserving array keys when normalize/denormalize variadic parameters.
And hopefully this will be helpful for community.

Next example shows everything I'd like to propose

   // serializer/Tests/Normalizer/AbstractNormalizerTest.php


    /**
     * @dataProvider getNormalizer
     */
    public function testVariadicSerializationWithPreservingKeys(AbstractNormalizer $normalizer): void
    {
        $d1 = new Dummy();
        $d1->foo = 'Foo';
        $d1->bar = 'Bar';
        $d1->baz = 'Baz';
        $d1->qux = 'Quz';
        $d2 = new Dummy();
        $d2->foo = 'FOO';
        $d2->bar = 'BAR';
        $d2->baz = 'BAZ';
        $d2->qux = 'QUZ';
        $arr = ["d1" => $d1, "d2" => $d2];
        $obj = new VariadicConstructorTypedArgsDummy(...$arr);

        $serializer = new Serializer([$normalizer], [new JsonEncoder()]);
        $normalizer->setSerializer($serializer);

        $this->assertEquals(
            '{"foo":{"d1":{"foo":"Foo","bar":"Bar","baz":"Baz","qux":"Quz"},"d2":{"foo":"FOO","bar":"BAR","baz":"BAZ","qux":"QUZ"}}}',
            $data = $serializer->serialize($obj, 'json')
        );
        
        // Currently the result of serialization will be as bellow (without preserving keys)
        /*
        {
            "foo": [
                {
                    "foo": "Foo",
                    "bar": "Bar",
                    "baz": "Baz",
                    "qux": "Quz"
                },
                {
                    "foo": "FOO",
                    "bar": "BAR",
                    "baz": "BAZ",
                    "qux": "QUZ"
                }
            ]
        }
        */

        $dummy = $normalizer->denormalize(json_decode($data, true), VariadicConstructorTypedArgsDummy::class);
        $this->assertInstanceOf(VariadicConstructorTypedArgsDummy::class, $dummy);
        $this->assertEquals($arr, $dummy->getFoo());
    }

Tested with

PHP: 8.1
symfony/serializer:6.2

Example

Seems like there is a simple change needed

// serializer/Normalizer/AbstractNormalizer.php::346

abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
{
// ...

    protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null)
    {
       // ... 
       // Line: 346
                        $variadicParameters = [];
                        // Fix is in respecting $parameterKey while denormalizing parameter
                        foreach ($data[$paramName] as $parameterKey => $parameterData) {
                            $variadicParameters[$parameterKey] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $attributeContext, $format);
                        }
       // ...                        
    }
// ...
}
nicolas-grekas added a commit that referenced this issue Mar 31, 2023
…c parameters (melya)

This PR was submitted for the 6.2 branch but it was squashed and merged into the 5.4 branch instead.

Discussion
----------

[Serializer] Preserve array keys while denormalize variadic parameters

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #49616
| License       | MIT
| Doc PR        | Let me know if needed
| PHP        | 8.1.15

Hi Symfony folks!

I've struggled with denormalization issue.
It appears when serialize an object with variadic parameter from associative array.
Currently, after denormalization array keys get reset and it is not compatible with previous version of object.

Example:
```php
<?php

declare(strict_types=1);

use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
use Symfony\Component\Serializer\Serializer;

require_once __DIR__ . "/vendor/autoload.php";

final class Dummy {
    public function __construct(public readonly string $i)
    {
    }
}

final class DummyWithVariadic {
    public $dummies;
    public function __construct(Dummy ...$dummies)
    {
        $this->dummies = $dummies;
    }
}

$serializer = new Serializer([$normalizer = new PropertyNormalizer()], [new JsonEncoder()]);
$normalizer->setSerializer($serializer);

$dummies = ["d1" => new Dummy("d1 value"), "d2" => new Dummy("d2 value")];
$object  = new DummyWithVariadic(...$dummies);
var_dump($object);
/* Output:
class DummyWithVariadic#310 (1) {
  public $dummies =>
  array(2) {
    'd1' =>
    class Dummy#308 (1) {
      public readonly string $i =>
      string(8) "d1 value"
    }
    'd2' =>
    class Dummy#309 (1) {
      public readonly string $i =>
      string(8) "d2 value"
    }
  }
}
*/

$json = $serializer->serialize($object, "json");
echo $json . PHP_EOL;
/* Output:
{"dummies":{"d1":{"i":"d1 value"},"d2":{"i":"d2 value"}}}
*/

$deserialized = $serializer->deserialize($json, DummyWithVariadic::class, "json");
var_dump($deserialized);
/* Output:
class DummyWithVariadic#315 (1) {
  public $dummies =>
  array(2) {
    [0] =>
    class Dummy#319 (1) {
      public readonly string $i =>
      string(8) "d1 value"
    }
    [1] =>
    class Dummy#320 (1) {
      public readonly string $i =>
      string(8) "d2 value"
    }
  }
}
*/

$jsonFromDeserialized = $serializer->serialize($deserialized, "json");
echo $jsonFromDeserialized . PHP_EOL;
/* Output:
{"dummies":[{"i":"d1 value"},{"i":"d2 value"}]}
*/

var_dump($json === $jsonFromDeserialized); // And now the deserialization result without respected array keys :(
/* Output:
bool(false)
*/
```

Let me know if we can add this fix for earlier versions 🙌
>  Bug fixes must be submitted against the lowest maintained branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).

I hope it's not , feedback is appreciated!
> Never break backward compatibility (see https://symfony.com/bc)

Commits
-------

c1f844a [Serializer] Preserve array keys while denormalize variadic parameters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants