Skip to content

[Serializer] Allow to provide a "normalizer" callable to normalize a property during denormalization #27933

Open
@ogizanagi

Description

@ogizanagi

Description

When dealing with DTOs & serializer and ObjectNormalizer, a missing feature to me is the ability to normalize values before actually setting the property in the DTOs. DTOs are often written with public properties only and writing custom setters (e.g: for trimming a string or change empty strings from the input to null) for it or a custom denormalizer is a bit overhead for the developer. The idea would be to be able to pass a normalizer callable in metadata that will be called by the ObjectNormalizer.

Another approach would be to rely on a PropertyAccessor annotation. #22190 inits the metadata support on this component, so it can fit as well.

Example

final class AddNotePayload
{
    /**
     * @var string
     *
     * @Assert\NotBlank()
     * @Serializer\Denormalizer("trim")
     */
    public $content;

    /**
     * @var \DateTime|null
     *
     * @Assert\Type(\DateTime::class)
     */
    public $createdAt;
}

or

final class AddNotePayload
{
    /**
     * @var string
     *
     * @Assert\NotBlank()
     * @PropertyAccessor(setterNormalizer="trim")
     */
    public $content;

    /**
     * @var \DateTime|null
     *
     * @Assert\Type(\DateTime::class)
     */
    public $createdAt;
}

Potential issues

When using AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true, we might get an unexpected type, thus the normalizer callable may fail due to incompatible type.

A solution might be to not execute the normalizer unless type is actually matching but then it probably has to live in the Serializer and not in the PropertyAccessor component.
Or the PropertyAccessor could catch any exception thrown by the normalizer callable and still set the value (could be configurable with a force option or whatever).

We might also provide a set of common normalizers that'll only act upon expected types and let the exception raise up if another user defined normalizer does not account properly for type/data (so native trim function usage would be discouraged in favor of a more clever PropertyAccessor\normalizers\trim() function).

In any case, proper validation must happen after and reject the non-matching value. So what we really want here is only trying to normalize a valid value (according to the type).

Metadata

Metadata

Assignees

No one assigned

    Labels

    FeatureKeep openRFCRFC = Request For Comments (proposals about features that you want to be discussed)Serializer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions