Skip to content

[Serializer] | [PropertyAccess] Add support to fold plain properties into objects during denormalization. #51510

@AlexMinaev19

Description

@AlexMinaev19

Description

Introduction

We already have the functionality to denormalize objects from different parts of data via #[SerializedPath('[some][path]')] and with different names via #[SerializedName('different_name)].
For example,

Let's imagine we have class Foo with the next structure:

class Foo
{
    public function __construct(
        #[SerializedPath('[internal][property]')] public string $bar,
        #[SerializedName('outter')] public string $some
    ) {
    }
}

and we have the next data to denormalize:

$data = [
    'internal' => [
        'property' => 'value1',
    ],
    'outter' => 'value2',
]

When we denormalize $data via $serializer we will get the object with desirable values:

//
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));

$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);

$serializer = new Serializer(
    [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)],
    ['json' => new JsonEncoder()]
);

$object = $serializer->denormalize($data, Foo::class);
$object->bar; // 'value1'
$object->some; // 'value2'

Problem description

But what about another case, when you have a plain array with multiple fields and you want to fold them into different objects?
For example, we have the following data to denormalize:

$plainData = [
    'foo' => 'value1',
    'bar' => 'value2',
    'baz' => 'value3',
    'bam' => 'value4',
    'zam' => 'value5',
    'tam' => 'value6',
    ... // and so on...
]

We can map all of these values to properties in one class, but I want to separate them by some meaning in different objects.
For example, I have the following domain classes:

class Bar
{
    public function __construct(
        public string $valueFive,
        public string $valueThree,
    ) {
    }
}

class Baz
{
    public function __construct(
        public string $valueOne,
        public string $valueSix,
        public string $valueTwo,
    ) {
    }
}

class Request
{
    public function __construct(
        public string $valueFour,  // keep fourth value in this class => can be done via #[SerializedName('bam')]
        public Bar $bar, // fold the properties 5 and 3 in the Bar::class => ?
        public Baz $baz // fold properties 1, 6, and 2 in the Baz::class => ?
    ) {
    }
}

Example

Possible solution

We can allow wildcard for #[SerializedPath]. For example, #[SerializedPath('[*]')] or #[SerializedPath('[]')] which can tell the serializer to look at properties in the current (root) scope.

Example:

$plainData = [
    'foo' => 'value1',
    'bar' => 'value2',
    'baz' => 'value3',
    'bam' => 'value4',
    'zam' => 'value5',
    'tam' => 'value6',
    ... // and so on...
]

class Bar
{
    public function __construct(
        #[SerializedName('zam')] public string $valueFive,
        #[SerializedName('baz')] public string $valueThree,
    ) {
    }
}

class Baz
{
    public function __construct(
        #[SerializedName('foo')] public string $valueOne,
        #[SerializedName('tam')] public string $valueSix,
        #[SerializedName('bar')] public string $valueTwo,
    ) {
    }
}

class Request
{
    public function __construct(
        #[SerializedName('bam')] public string $valueFour,
        #[SerializedPath('[*]')] public Bar $bar,
        #[SerializedPath('[*]')] public Baz $baz 
    ) {
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions