Skip to content

Weak-mode scalar deserializers cast null to 0/0.0/false/"" on nullable fields #110

@waahhhh

Description

@waahhhh

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 ?float0.0, ?boolfalse, 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions