$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[September 05, 2025] Eloquent ORM

Eloquent JSON Casting

Eloquent JSON Casting: Tips & Tricks

────────────────────────────────────────────────────────

Eloquent JSON Casting: Tips & Tricks

Tip: Cast to Array for JSON Columns

protected $casts = [
    'settings' => 'array',
];

Automatically encodes on save, decodes on access.

Gotcha: JSON Queries are Database-Specific

// MySQL
User::where('settings->theme', 'dark')->get();

// PostgreSQL
User::where('settings->>theme', 'dark')->get();

The syntax differs between databases.

Tip: Cast to Object for Property Access

protected $casts = [
    'settings' => 'object',
];

$user->settings->theme; // Instead of $user->settings['theme']

Gotcha: Modifying Array Casts Doesn't Auto-Save

$settings = $user->settings;
$settings['theme'] = 'dark';
// $user->save() won't detect the change
$user->settings = $settings; // Reassign to trigger dirty detection
$user->save();

Tip: JSON Where Clauses

User::whereJsonContains('roles', ['admin'])->get();
User::where('settings->notifications->email', true)->get();

Gotcha: Null JSON Columns

If the column is null, accessing it returns null, not an empty array. Use a default:

protected $casts = [
    'settings' => 'array',
];

protected $attributes = [
    'settings' => '{}',
];

Tip: Encrypted JSON

protected $casts = [
    'secrets' => 'encrypted:array',
];

JSON data encrypted at rest.

Tip: Use cursor() for Memory-Neutral Iteration

When exporting 100K rows, get() loads everything into memory. cursor() uses yield and keeps memory flat regardless of row count. Perfect for artisan commands.

Tip: whereHas() vs load() — Two Different Things

whereHas() filters the parent query by relationship existence. load() eager-loads relationships AFTER the query. Mixing them up is a common source of logic bugs.

Gotcha: withCount() Adds a Subquery

withCount('comments') runs a correlated subquery on every row. On large tables, this can be slower than a separate query. Profile before relying on it.

Senior Insight

JSON columns in Eloquent are convenient but they're a storage anti-pattern when used as a replacement for proper normalization. I've inherited databases with JSON columns containing hundreds of fields, none of which were indexable or queryable without LIKE hacks. Use JSON columns for simple configuration data or flexible metadata, not for structured data that should be in related tables. If you find yourself querying inside a JSON column, it's time to normalize.

Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)

────────────────────────────────────────────────────────
<-- back to posts