rompetomp / inertia-bundle

Inertia.js server-side adapter for Symfony
MIT License
153 stars 41 forks source link

Impossible to get an empty JSON object in the generated response #21

Closed aleksblendwerk closed 4 years ago

aleksblendwerk commented 4 years ago

Hi Hannes,

not sure if you are up for this lengthy and rather inconvenient issue - as you said you were not using the project any more - but I'll try my luck anyway.

TLDR: see the PR, I guess... #20

I am currently using the bundle in a project where the Vue side of things expects an empty errors object to determine that there were no errors.

In the current state of the bundle, it seems impossible to get an empty object - in my case it's just a new ArrayObject() - all the way through to the JSON string that is finally returned by the render method of Inertia and I always end up with this warning in the console:

[Vue warn]: Invalid prop: type check failed for prop "errors". Expected Object, got Array

If my findings 🕵🏻‍♂️ are correct, this is due to two things:

I. When Symfony's Serializer is found in the serialize method of Inertia (as in my case), the objects in $page eventually make their way through AbstractObjectNormalizer which will return an empty array for empty objects - unless the key preserve_empty_objects is present in the context.

Relevant excerpt from AbstractObjectNormalizer:

    $data = [];

    [...]

    if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) {
        return new \ArrayObject();
    }

    return $data;

So my first proposal would be to add preserve_empty_objects to the context that is passed from the serialize method in Inertia, like so:

    if (null !== $this->serializer) {
        $json = $this->serializer->serialize($page, 'json', array_merge([
            'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
            AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function () { return null; },
            AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
        ], $context));
    } else {
        $json = json_encode($page);
    }

(Sidenote: if the Serializer is not found and json_encode is used instead, all is fine so far. But that's unlikely in a Symfony project...)

All right, one down.

II. Now at the very end of the serialize method, this happens:

    return json_decode($json, true) ?? [];

The second parameter is true, meaning JSON objects will be returned as associative arrays (as the PHP docs tell us), so this will once again turn all our carefully preserved objects in $json to arrays.

I don't think this was intended and you mainly set it to true to be able to return an array instead of the odd stdClass object that json_decode gives us when this is not set.

I think we can fix this by just replacing it with:

    return (array) json_decode($json, false);

Now I am finally getting my desired empty object in the final response sent by Symfony.

The downside is: this might break things for users of your bundle who are (unknowingly) relying on this behavior. But I would assume it was never your intention to actually cast or manipulate any objects or types.

What do you think?

Also maybe @innocenzi has some thoughts on this as well, as he added the Serializer feature.

rompetomp commented 4 years ago

Hi Aleks

Thanks for this, I follow you 100% and I will merge your PR. It seems best practice that an empty object would still be an object, instead of suddenly an array. I decided to tag the version to 2.0.0, so people who are relying on this "feature" won't have issues (this is also best practice according to semver).

Again, thanks for the provided work. Next time, just put this text in the Pull Request, that'll keep everything together nicely ;-)

aleksblendwerk commented 4 years ago

Next time, just put this text in the Pull Request, that'll keep everything together nicely ;-)

Good call, will do that from now on! Thanks again for the quick merge and release!