framework icon indicating copy to clipboard operation
framework copied to clipboard

jsonSerialize() on relations is not called when serializing the parent model

Open clementbirkle opened this issue 7 months ago • 5 comments

Laravel Version

12.15.0

PHP Version

8.3.20

Database Driver & Version

No response

Description

When calling toJson() or jsonSerialize() on an Eloquent model that has loaded relationships, the jsonSerialize() method of the related model(s) is not invoked. Instead, the default serialization behavior is used for the related models, which bypasses any custom serialization logic defined in their jsonSerialize() method.

This is inconsistent with what happens when the related model is serialized independently, where jsonSerialize() is correctly called.

This makes it impossible to customize the JSON representation of related models when serializing the parent, which is unexpected and inconsistent.

Steps To Reproduce

/**
 * @property string $name
 * @property-read \App\Models\UserConfig $config
 */
class User extends Model
{
    public function config(): HasOne
    {
        return $this->hasOne(UserConfig::class);
    }
}

/**
 * @property array $custom_fields
 */
class UserConfig extends Model
{
    public function jsonSerialize(): array
    {
        return [
            'customFields' => (object) $this->custom_fields,
        ];
    }
}

// Serializing the related model directly works as expected:
$userConfig = UserConfig::find(1);
echo $userConfig->toJson(); // {"customFields":{}}
// ✅ jsonSerialize is called

// Serializing the parent model that has a loaded relation does not:
$user = User::with('config')->find(1);
echo $user->toJson(); // {"name":"John Doe","config":{"custom_fields":[]}}
// ❌ jsonSerialize on UserConfig is NOT called

Expected Behavior:

The jsonSerialize() method of related models should be invoked when the parent model is serialized.

Current Behavior:

Related models are serialized using the default Eloquent logic, ignoring any custom jsonSerialize() methods defined on them.

clementbirkle avatar May 22 '25 14:05 clementbirkle

Hi @clementbirkle As I understand Eloquent uses toArray() for relationships, not jsonSerialize(). So instead of overriding jsonSerialize(), you can override toArray() in your related models to ensure consistent serialization.

this is a common workaround in Laravel and is considered best practice for custom serialization logic.

egyjs avatar May 22 '25 18:05 egyjs

Hi @egyjs, thank you for your response.

I’d like to add a couple of points:

  1. In some cases, we need different behavior between ->toArray() and ->toJson(). In my example, overriding toArray() would force me to return a structure like "customFields" instead of "custom_fields", and cast the array to an object—this would apply to all conversions, even where only toJson() should be affected. This reduces flexibility and leads to unnecessary coupling.

  2. The jsonSerialize() method is documented in the Laravel documentation, so it’s reasonable to expect that it would be respected across all serialization contexts, including when serializing related models. Ignoring it in relationships introduces an inconsistency that's hard to debug.

I’d be happy to create a PR—it’s a relatively simple change, and I believe it would provide a cleaner and more elegant solution than overriding toArray() in cases where only JSON output needs customization.

Would the Laravel team be open to a PR for this enhancement?

clementbirkle avatar May 22 '25 19:05 clementbirkle

While it may seem expected, but application may already expect the current behaviour to persist so this is best as a PR to master branch.

crynobone avatar May 23 '25 07:05 crynobone

Hi @clementbirkle You may want to use API Resources which allow you to format JSON in the way you need

asokol1981 avatar May 24 '25 15:05 asokol1981

Hi @clementbirkle You may want to use API Resources which allow you to format JSON in the way you need

Thank you, @asokol1981. Yes, API Resources or Spatie Data are great for handling this scenario. However, for older projects that don’t already use them, or for very small projects, I think it should still be possible to work directly with the model and use the jsonSerialize method.

clementbirkle avatar May 26 '25 09:05 clementbirkle