Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Symfony serializer does not map collections correctly #27279

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

Closed
fhink opened this issue May 16, 2018 · 7 comments
Closed

Symfony serializer does not map collections correctly #27279

fhink opened this issue May 16, 2018 · 7 comments

Comments

@fhink
Copy link

fhink commented May 16, 2018

Symfony version(s) affected: 4.1.0

Description
When there is a collection with a single item, the serializer creates produces an error instead of an array with one object.

Fatal error: Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: The type of the key "company" must be "int" ("string" given). in /Users/f.hink/www/collection_mapping_bug/Normalizer/ArrayDenormalizer.php:57
Stack trace:
#0 /Users/f.hink/www/collection_mapping_bug/Serializer.php(172): Symfony\Component\Serializer\Normalizer\ArrayDenormalizer->denormalize(Array, 'App\\Entities\\Of...', 'xml', Array)
#1 /Users/f.hink/www/collection_mapping_bug/Normalizer/AbstractObjectNormalizer.php(271): Symfony\Component\Serializer\Serializer->denormalize(Array, 'App\\Entities\\Of...', 'xml', Array)
#2 /Users/f.hink/www/collection_mapping_bug/Normalizer/AbstractObjectNormalizer.php(202): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize('App\\Entities\\Of...', 'office', Array, 'xml', Array)
#3 /Users/f.hink/www/collection_mapping_bug/Serializer.php(172): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denorm in /Users/f.hink/www/collection_mapping_bug/Normalizer/ArrayDenormalizer.php on line 57

How to reproduce
https://github.com/fhink/symfony-collection-serializer-bug
Just run php src/serialize.php

Possible Solution
fhink/symfony-collection-serializer-bug@2f5984b

Additional context
Tested with PHP 7.1+

https://stackoverflow.com/questions/49816306/symfony-serializer-deserialise-xml-which-contains-an-element-with-variable-amoun

@webnet-fr
Copy link
Contributor

Yes it definetly looks like a bug. Current implementation of XmlEncoder decodes

<restaurants>
  <restaurant>
    <name>Some restaurant name</name>
    <type>Chinese</type>
  </restaurant>
  <restaurant>
    <name>Another restaurant name</name>
    <type>Italian</type>
  </restaurant>
</restaurants>

as

restaurants = {array} [1]
 restaurant = {array} [2]
  0 = {array} [2]
   name = "Some restaurant name"
   type = "Chinese"
  1 = {array} [2]
   name = "Another restaurant name"
   type = "Italian"

At the same time this xml

<restaurants>
  <restaurant>
    <name>Some restaurant name</name>
    <type>Chinese</type>
  </restaurant>
</restaurants>

will be decoded to:

restaurants = {array} [1]
 restaurant = {array} [2]
  name = "Some restaurant name"
  type = "Chinese"

This situation seems natural as there is no way in xml to indicate that a particular node is an element of a collection (unlike yaml or json).
Your possible solution seems ok because IMHO there is nothing to do with XmlEncoder because it is unaware of underlying type of restaurants. However I would wrap data in array only for xml format:

if ('xml'=== $format && !is_int(key($data))) {
    $data = array($data);
}

Otherwise two different yaml snippets :

restaurants:
  restaurant:
    - name: Some restaurant name
      type: Chinese

and

restaurants:
  restaurant:
    name: Some restaurant name
    type: Chinese

would be deserialized without any problem though the second snippent is not quite appropriate for your model.

Status: Reviewed

@nicolas-grekas
Copy link
Member

Thanks for the report and the confirmation. Would any of you like to submit a fix?

fabpot added a commit that referenced this issue Jun 10, 2018
…ontains the only one element (webnet-fr)

This PR was squashed before being merged into the 3.4 branch (closes #27326).

Discussion
----------

[Serializer] deserialize from xml: Fix a collection that contains the only one element

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #27279
| License       | MIT
| Doc PR        |

In xml when parent node (`restaurants`) contains several children nodes with the same tag (`restaurant`) it is clear that the children form a collection:

```
restaurants = {array} [1]
 restaurant = {array} [2]
  0 = {array} [2]
   name = "Some restaurant name"
   type = "Chinese"
  1 = {array} [2]
   name = "Another restaurant name"
   type = "Italian"
```

Afterwards the object denormalizer has no problem to create a collection of restaurants.

But when there is only one child (`restaurant`) the decoded normalized array will not contain a collection:

```
restaurants = {array} [1]
 restaurant = {array} [2]
  name = "Some restaurant name"
  type = "Chinese"
```

In this situation the object denormalizer threw unexpected exception. This PR modifies `AbstractObjectNormalizer` that is it will fill a collection containing the sole element properly.

Commits
-------

1f346f4 [Serializer] deserialize from xml: Fix a collection that contains the only one element
@fabpot fabpot closed this as completed Jun 10, 2018
@nano-freelancer
Copy link

I ask @fabpot attention to reopen issue

Faced the same issue, but now for the json-data(nested object)
{"object1":{"object2":[{"key1":0,"key2":1,"key3":2,"key4":3},{"key1":4,"key2":5,"key3":6,"key4":7}]}}

Same error:
Fatal error: Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: The type of the key "object2" must be "int" ("string" given)

Fix provided in commit 1f346f4 works well if remove xml condition. I believe it is necessary to expand fix for json too.

@nicolas-grekas
Copy link
Member

@nano-freelancer if you want anyone to really care, you'd better submit a new issue with detailed description. If you can submit a fix, that'd be even better. Thanks.

@nano-freelancer
Copy link

@nicolas-grekas done #28550

@dunglas
Copy link
Member

dunglas commented Sep 23, 2018

@nano-freelancer can you post the corresponding PHP entity please?

@nano-freelancer
Copy link

@nano-freelancer can you post the corresponding PHP entity please?

namespace App\Model;

use Doctrine\Common\Collections\ArrayCollection;

class ApiCall
{
    public $object1;

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

    public function getObject1() { 
         return $this->object1; 
    }
    public function setObject1($object1) { 
         $this->object1 = $object1; 
    }
    public function addObject1(Object1 $object1) { 
         if ($this->object1->contains($object1)) {
             return;
         }
         $this->object1->add($object1);
    }

}

class Object1
{
    public $object2;

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

    public function getObject2() { 
         return $this->object2; 
    }
    public function setObject2($object2) { 
         $this->object2 = $object2;
    }

    public function addObject2(Object2 $object2) {
         if ($this->object2->contains($object2)) {
             return;
         }
         $this->object2->add($object2);
    }
}

class Object2
{
    public function getKey1() { 
         return $this->key1; 
    }
    public function setKey1($key1) { 
         $this->key1 = $key1; 
    }
    public $key1; //String

    public function getKey2() { 
         return $this->key2; 
    }
    public function setKey2($key2) { 
         $this->key2 = $key2; 
    }
    public $key2; //int

    public function getKey3() { 
         return $this->key3; 
    }
    public function setKey3($key3) { 
         $this->key3 = $key3; 
    }
    public $key3; //String

}

in controller:
$result = $serializer->deserialize($data, ApiCall::class, 'json');

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

8 participants