Description
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
) {
}
}