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

Skip to content

[Serializer] Support for array denormalization #14343

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
wants to merge 2 commits into from

Conversation

derrabus
Copy link
Member

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets none
License MIT
Doc PR none (yet)

I had to implement a couple of API endpoints that receive data as JSON-serialized arrays of objects. The current implementation of the Serializer component is capable of serializing such arrays, but so far this operation is a one-way-road. This PR is an attempt to change this.

Demo

class Dummy
{
    public $foo;
    public $bar;

    public function __construct($foo = null, $bar = null)
    {
        $this->foo = $foo;
        $this->bar = $bar;
    }
}

$serializer = new \Symfony\Component\Serializer\Serializer(
    array(
        new \Symfony\Component\Serializer\Normalizer\PropertyNormalizer(),
        new \Symfony\Component\Serializer\Normalizer\ArrayDenormalizer()
    ),
    array(
        new \Symfony\Component\Serializer\Encoder\JsonEncoder()
    )
);

$json = $serializer->serialize(
    array(
        new Dummy('one', 'two'),
        new Dummy('three', 'four')
    ),
    'json'
);

echo $json . "\n\n";

// Deserialize the JSON string, so we get back to where we started from.
$data = $serializer->deserialize($json, 'Dummy[]', 'json');

var_dump($data);

By appending [] to the type parameter, you indicate that you expect to deserialize an array of objects of the given type. This is the same notation that phpDocumentor uses to indicate collections. The denormalization of the array is implemented recursively: The denormalizer simply calls Serializer::denormalize() on each element of the array. This way, the ArrayDenormalizer can be combined with any other denormalizer.

Side effects

For this implementation, I had to touch GetSetMethodNormalizer, PropertyNormalizer and CustomNormalizer. Those classes expected supportsDenormalization to be called with a valid class in the $type parameter. Instead of throwing a reflection exception, they now simply return false. I'm not exactly sure, if this is should be considered to be a BC break.

This implementation violates the SerializerInterface which declared deserialize() to always return an object. But imho, the assumption that serialized data always represents an object is too restrictive anyway. Also, this declaration is not consistent with the serialize() method which accepts mixed as input type for the data to serialize.

Other PRs

I've found an older PR adressing this issue, #12066. That PR was closed because the contributor did not reply to feedback.

@derrabus
Copy link
Member Author

ping @dunglas

@dunglas
Copy link
Member

dunglas commented Apr 14, 2015

Thanks for your contribution @derrabus.

Instead of creating a new ArrayDenormalizer, what do you think of applying the strategy already in use for normalization: https://github.com/symfony/symfony/blob/2.7/src/Symfony/Component/Serializer/Serializer.php#L133-L150

In short:

  1. Give a chance to any registered normalizer to denormalize the given structure (even if it's an array)
  2. If no registered normalizer is able to denormalize the given data: the serializer test if the given data is an array and try to denormalize each value of that array. It returns an array of objects.

I see some advantages to this method:

  • it is similar and mirrors the design of the Serializer::normalize() method
  • no need fo the [] syntax, both $serializer->denormalize('[{ "foo": "bar"}]', 'Dummy'); and $serializer->denormalize('{ "foo": "bar"}', 'Dummy'); will work (the first will return an array and the second a Dummy instance).
  • no need to register another custom normalizer (will work out of the box with current Serializer installs)

I fully agree with you to update the PHPDoc of SerializerInterface::denormalize() to object|array.

@stof
Copy link
Member

stof commented Apr 14, 2015

@dunglas why would $serializer->denormalize('[{ "foo": "bar"}]', 'Dummy'); return an object ? The method call is explicitly asking to denormalize into a Dummy object.

@dunglas
Copy link
Member

dunglas commented Apr 14, 2015

@stof IMO it should return an array of Dummy objects... Or throw an error. Don't know what is the more DX-friendly.

Actually, the normalize method will return a JSON array if you pass an array of objects as first parameter even if this is not asked explicitly.

@derrabus
Copy link
Member Author

@dunglas I'd prefer the Dummy[] notation. I usually know what kind of data I'm deserializing, so a magic auto-detection of arrays would have no use-case for me. Since normalized objects are also arrays, I see a potential source of nasty bugs in such a functionality.

My soloution would also allow to denormalize something like Dummy[][]. Don't know, if that's useful, though. 😉

Of course, I could easily merge ArrayDenormalizer into the Serializer class. Keeping the code outside the Serializer was the cleaner soloution, imho. But I could live with merging it.

If you want this functionality to work out of the box, we could patch FrameworkBundle to always register the ArrayDenormalizer if the serializer is enabled.

@dunglas
Copy link
Member

dunglas commented Apr 15, 2015

@derrabus and what do you think about removing the array normalization logic from the Serializer class and move it to your new ArrayNormalizer with all needed deprecation notices?

@derrabus
Copy link
Member Author

I thought about that as well, but I decided not to do it as this would break backwards compatibility. So far, this patch keeps BC, as far as I can tell. Maybe we should create a follow-up PR for moving the array normalization logic out of the Serializer if we really want to do this.

@dunglas
Copy link
Member

dunglas commented May 24, 2015

@derrabus can you rebase to make Travis happy?

@derrabus
Copy link
Member Author

@dunglas Sure. Should I rebase the patch against the 2.8 branch? Currently, it's based on 2.7 and that branch is probably feature frozen by now.

@dunglas
Copy link
Member

dunglas commented May 26, 2015

Yes please.

@derrabus
Copy link
Member Author

Rebased against 2.8, new PR #14756.

@derrabus derrabus closed this May 26, 2015
dunglas added a commit that referenced this pull request May 27, 2015
This PR was merged into the 2.8 branch.

Discussion
----------

[Serializer] Support for array denormalization

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | none
| License       | MIT
| Doc PR        | none (yet)

This is a rebase of #14343 against the 2.8 branch.

I had to implement a couple of API endpoints that receive data as JSON-serialized arrays of objects. The current implementation of the Serializer component is capable of serializing such arrays, but so far this operation is a one-way-road. This PR is an attempt to change this.

## Demo
```php
class Dummy
{
    public $foo;
    public $bar;

    public function __construct($foo = null, $bar = null)
    {
        $this->foo = $foo;
        $this->bar = $bar;
    }
}

$serializer = new \Symfony\Component\Serializer\Serializer(
    array(
        new \Symfony\Component\Serializer\Normalizer\PropertyNormalizer(),
        new \Symfony\Component\Serializer\Normalizer\ArrayDenormalizer()
    ),
    array(
        new \Symfony\Component\Serializer\Encoder\JsonEncoder()
    )
);

$json = $serializer->serialize(
    array(
        new Dummy('one', 'two'),
        new Dummy('three', 'four')
    ),
    'json'
);

echo $json . "\n\n";

// Deserialize the JSON string, so we get back to where we started from.
$data = $serializer->deserialize($json, 'Dummy[]', 'json');

var_dump($data);
```

By appending `[]` to the type parameter, you indicate that you expect to deserialize an array of objects of the given type. This is the same notation that phpDocumentor uses to indicate collections. The denormalization of the array is implemented recursively: The denormalizer simply calls `Serializer::denormalize()` on each element of the array. This way, the ArrayDenormalizer can be combined with any other denormalizer.

## Side effects

For this implementation, I had to touch `GetSetMethodNormalizer`, `PropertyNormalizer` and `CustomNormalizer`. Those classes expected `supportsDenormalization` to be called with a valid class in the `$type` parameter. Instead of throwing a reflection exception, they now simply return false. I'm not exactly sure, if this is should be considered to be a BC break.

This implementation violates the `SerializerInterface` which declared `deserialize()` to always return an object. But imho, the assumption that serialized data always represents an object is too restrictive anyway. Also, this declaration is not consistent with the `serialize()` method which accepts `mixed` as input type for the data to serialize.

## Other PRs

I've found an older PR adressing this issue, #12066. That PR was closed because the contributor did not reply to feedback.

Commits
-------

0573f28 Support for array denormalization.
@derrabus derrabus deleted the 2.7-array-denormalization branch May 27, 2015 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants