Quentin Delcourt

Don't call jsonSerialize() explicitly

PHP's JsonSerializable interface can sometimes be misunderstood.
TL;DR don't call `jsonSerialize()` directly, always pass the object implementing it to the `json_encode` function.

JsonSerializable is a handy PHP feature that was introduced in PHP 5.4. It lets you define how an object should be transformed into JSON when passed to the json_encode function. For example, if you were to order a box of chocolate and send a preview of it to someone you offer it to, you might want to show the content of the box but not its price.

By defining an array of properties (but the return value could be anything that could be serialized to JSON), you get to decide what information will be returned in the resulting JSON:

final class ChocolateBox implements JsonSerializable
{
    private int $price;
    private array $chocolates;

    /* Magic code to build the box... */

    public function jsonSerialize() : array
    {
        return [
            'chocolates' => $this->chocolates,
        ];
    }
}

So far, so good. And then, in the client, usually a controller, I sometimes see this:

final class ChocolateBoxController
{
    public function indexAction(Request $request, ChocolateBoxService $chocolateBoxService) : Response
    {
        $chocolateBox = $chocolateBoxService->getRandomBox();

        return new Response(
            json_encode($chocolateBox->jsonSerialize())
        );
    }
}

👆 Can you spot the problem in the code above?

We're calling json_encode and passing it the result of the jsonSerialize method. That's not the idea... json_encode actually knows about the existence of the function if the object it's being passed implements the JsonSerializable interface.

Therefore, simply passing the object to json_encode is enough:

json_encode($chocolateBox)

👇 gives us

{
    "chocolates" => [/* delicious list of chocolates */]
}

It even works when you bury the object in another structure, such as an array:

json_encode([
    'data' => $chocolateBox,
])

👇 gives us

{
    "data": {
        "chocolates" => [/* delicious list of chocolates */]
    }
}

By implementing the interface you allow json_encode to get what it needs out of an object. Your work stops right after that, let json_encode do the rest...