Detailed description
Provide a detailed description of the change or addition you are proposing.
Make it clear if the issue is a bug, an enhancement or just a question.
Description
When a scalar field is declared nullable (e.g. ?int) and marked #[Field(strict: false)], deserializing a null value yields 0 instead of null. The same happens for ?float → 0.0, ?bool → false, and ?string → "".
Reproduction
#[ClassSettings(requireValues: true)]
class Foo
{
public function __construct(
#[Field(strict: false)]
public ?int $bar = null,
) {}
}
$result = $serde->deserialize(
serialized: ['bar' => null],
from: 'array',
to: Foo::class,
);
// Expected: Foo { bar: null }
// Actual: Foo { bar: 0 }
Cause
In Crell\Serde\Formatter\ArrayBasedDeformatter, the strict branch of each scalar deserializer correctly checks $field->nullable && is_null($value), but the weak-mode branch casts the value unconditionally:
// ArrayBasedDeformatter::deserializeInt()
if ($field->strict) {
if (!is_int($value) && !($field->nullable && is_null($value))) {
throw TypeMismatch::create(...);
}
return $decoded[$field->serializedName];
}
// Weak mode — no nullable/null guard, so (int)null === 0
return (int)($decoded[$field->serializedName]);
deserializeFloat(), deserializeBool(), and deserializeString() have the same shape.
Context
Why is this change important to you? How would you use it?
Laravel always provide query parameters as strings.
Even when using a FormRequest validation such as integer with $request->validated().
That is why it is necessary to use strict: false in the DTOs.
How can it benefit other users?
This is a incorrect behavior that also affects other users.
Possible implementation
Not obligatory, but suggest an idea for implementing addition or change.
Suggested fix
Guard the weak-mode cast with the same nullable check used in strict mode:
if ($field->nullable && is_null($value)) {
return null;
}
return (int) $value;
Applied symmetrically to all four scalar deserializers.
Additionally, use the variable $value in place of $decoded in the lines following the initialization of $value.
Your environment
Include as many relevant details about the environment you experienced the bug in and how to reproduce it.
- Version used (e.g. PHP 5.6, HHVM 3): PHP 8.4.20 (cli) (built: Apr 11 2026 07:43:26) (NTS)
- Operating system and version (e.g. Ubuntu 16.04, Windows 7): Ubuntu 24.04.4 LTS
- Library Version: 1.5.0
Detailed description
Provide a detailed description of the change or addition you are proposing.
Make it clear if the issue is a bug, an enhancement or just a question.
Description
When a scalar field is declared nullable (e.g.
?int) and marked#[Field(strict: false)], deserializing anullvalue yields0instead ofnull. The same happens for?float→0.0,?bool→false, and?string→"".Reproduction
Cause
In
Crell\Serde\Formatter\ArrayBasedDeformatter, the strict branch of each scalar deserializer correctly checks$field->nullable && is_null($value), but the weak-mode branch casts the value unconditionally:deserializeFloat(),deserializeBool(), anddeserializeString()have the same shape.Context
Why is this change important to you? How would you use it?
Laravel always provide query parameters as strings.
Even when using a
FormRequestvalidation such asintegerwith$request->validated().That is why it is necessary to use
strict: falsein the DTOs.How can it benefit other users?
This is a incorrect behavior that also affects other users.
Possible implementation
Not obligatory, but suggest an idea for implementing addition or change.
Suggested fix
Guard the weak-mode cast with the same nullable check used in strict mode:
Applied symmetrically to all four scalar deserializers.
Additionally, use the variable
$valuein place of$decodedin the lines following the initialization of$value.Your environment
Include as many relevant details about the environment you experienced the bug in and how to reproduce it.