Laravel Version
13.7.6
PHP Version
8.5.2
Database Driver & Version
MySQL 9.3.0
Description
I've noticed that there's a difference between string-based casts such as array and object vs class-based casts such as AsArrayObject and AsCollection if the target column is of type JSON.
When persisting null, they act differently:
$model->jsonColumn = null;
$model->save();
| Cast |
Database value |
array / object |
null |
AsArrayObject / AsCollection |
"null" (JSON type) |
When working with retrieved models all works fine because the JSON type is properly converted back to plain null. But things get skewed when you want to query for the json column being entirely null:
| Cast |
Query |
MySQL |
SQLite |
array / object |
whereNull(jsonColumn) |
✅ |
✅ |
AsArrayObject / AsCollection |
whereNull(jsonColumn) |
❌ |
❌ |
AsArrayObject / AsCollection |
where(jsonColumn, '=', '"null"') |
❌ |
❌ |
AsArrayObject / AsCollection |
whereJsonLength(jsonColumn, '<=', 0) |
❌ |
✅ |
AsArrayObject / AsCollection |
whereRaw('JSON_TYPE(jsonColumn) = "NULL"') |
✅ |
❌ |
The main difference is that the string-based casts don't cast if the value is null. They keep it as null in the database as well, resulting in whereNull to be able to query it. The class-based casts do apply their casting, in this case to JSON string "null".
Given that whereNull(jsonColumn->property) IS supported, I would have expected whereNull(jsonColumn) to be supported as well. And also note the difference in results between MySQL en SQLite (haven't checked Postgres or SQL Server yet), denoting a database driver dependency in codebases if the grammars are left as-is.
Steps To Reproduce
See https://github.com/Propaganistas/framework-issue-61204 for a reproducible application.
Schema::create('foos', function (Blueprint $table) {
$table->id();
$table->json('items')->nullable(); // but nullable isn't even required here because it will always contain a json type string, even "null"
$table->timestamps();
});
class Foo extends Model
{
protected $guarded = [];
protected function casts(): array
{
return [
'items' => AsCollection::class,
];
}
}
$foo = new Foo;
$foo->items = null;
$foo->save();
Foo::whereNull('items')->exists(); // false
Laravel Version
13.7.6
PHP Version
8.5.2
Database Driver & Version
MySQL 9.3.0
Description
I've noticed that there's a difference between string-based casts such as
arrayandobjectvs class-based casts such asAsArrayObjectandAsCollectionif the target column is of type JSON.When persisting
null, they act differently:array/objectnullAsArrayObject/AsCollection"null" (JSON type)When working with retrieved models all works fine because the JSON type is properly converted back to plain
null. But things get skewed when you want to query for the json column being entirelynull:array/objectwhereNull(jsonColumn)AsArrayObject/AsCollectionwhereNull(jsonColumn)AsArrayObject/AsCollectionwhere(jsonColumn, '=', '"null"')AsArrayObject/AsCollectionwhereJsonLength(jsonColumn, '<=', 0)AsArrayObject/AsCollectionwhereRaw('JSON_TYPE(jsonColumn) = "NULL"')The main difference is that the string-based casts don't cast if the value is
null. They keep it asnullin the database as well, resulting inwhereNullto be able to query it. The class-based casts do apply their casting, in this case to JSON string "null".Given that
whereNull(jsonColumn->property)IS supported, I would have expectedwhereNull(jsonColumn)to be supported as well. And also note the difference in results between MySQL en SQLite (haven't checked Postgres or SQL Server yet), denoting a database driver dependency in codebases if the grammars are left as-is.Steps To Reproduce
See https://github.com/Propaganistas/framework-issue-61204 for a reproducible application.