-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Serializer] when normalizing, order the properties in a way controllable by the user #27441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@dkarlovi I never encountered a case where the order of the properties mattered, in a json, yaml or xml. |
@Einenlum The order is for humans. If you have an API (say, built on API Platform), it makes sense for you to be specific in the order of the properties (for example, the more common ones on top, group similar properties together, etc). Since the order exists and is not arbitrary anyway (it doesn't change between refreshes), it would make sense to allow the user to control it. |
If you serialize data as CSV you'll expect to be able to choose the order of the fields (at least an order you can expect or an explanation about the generated order). In my case i have something like : $serializer->serialize(
$collectionOfData,
'csv',
[
'attributes' => [
'id',
'name',
'description',
'year',
'actors' => [
'type',
'name',
],
'location' => [
'address',
'city',
'country',
'zipCode',
],
'sizing' => [
'length',
'volume',
'surface',
'outflow',
'other',
],
],
]
); It will generate a csv like this :
Why does |
@Dranac here, a workaround to generate your csv with the ordered fields (described in the private namespace App\Serializer\Normalizer;
use App\Entity\Foo;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class CsvFooNormalizer implements NormalizerInterface
{
private $normalizer;
private $order = ['a', 'b', 'c'];
public function __construct(ObjectNormalizer $normalizer)
{
$this->normalizer = $normalizer;
}
public function normalize($objects, $format = null, array $context = array()): array
{
$data = [];
foreach ($objects as $object) {
$ordered = array_merge(array_flip($this->order), $this->normalizer->normalize($object, $format, $context));
}
return $data;
}
public function supportsNormalization($data, $format = null): bool
{
return is_array($data) && $data[0] instanceof Foo && 'csv' === $format;
}
} |
@maidmaid creating a custom normalizer for each class you want to serialize just to manually set the order is not great, not to mention the performance implications this approach might have, doesn't seem like a good workaround, let alone what I'd consider a solution. |
+1 this issue because I am in the exact situation: Wanting to reorder the properties for a CSV. |
Amazon MWS Feed xml format! |
Same issue here.. need to have specific order of properties in csv |
+1 Workaround: Change the order of your getters and setters within your entity. |
Our system has clients which require a fixed order of XML fields. They are using some funny XML parsers or configurations which rely on the order. Unfortunately we have to consider the order. It worked fine using the JMS parser before, which supports this feature. After upgrading to Symfony parser we had set the order of getters and setters right (as mentioned by @Surf-N-Code). However, this leads to some complications when for example extending a class. If I want to have a different order for a field in my child class, I'd have to copy all the functions. It would be nice to see a solution like the JMS has, an annotation which allows you to specify the order. |
We could use the order defined in XML files, but it will not work for annotations (as annotations use the reflection API under the hood, the order will be the same as currently). Also, the order doesn't matter when serializing JSON documents (except for humans) because by the spec JSON objects are unordered. The order matters however only if you use XML or CSV. To be honest, I think that when the order matters and isn't the same as the one of the properties (or accessors) in the class, using a dedicated DTO matching the excepted output would be cleaner than introducing an "order" or a "weight" key on the annotation, but I'm not strongly opposed to adding such key anyway. Regarding other config formats than annotations, I´le not sure if changing the order to match the one in the config file would be allowed by our BC promise. |
@dunglas we can always opt in. But also, since this order is not deliberate, it does seem like it wouldn't be part of it. |
Why isn't it deliberate? Having the same order than the one is the class looks rational to me. |
In case of using configuration it's definitely not expected (hence this issue) since you talk about the serialization in the configuration, not the class. In case of annotations, I agree with you.
Not necessarily, I can see something like |
Alternatively, we can use
|
I faced again the same need more than 1 year after my first comment 😄 This time, I solved it by creating a normalizer decorator handling sort. So, as any decorator, it can be easily configurated in the DI with your custom normalizers. class SortedNormalizer implements NormalizerInterface
{
private $decorated;
private $sort;
public function __construct(NormalizerInterface $decorated, array $sort)
{
$this->decorated = $decorated;
$this->sort = array_flip($sort);
}
public function normalize($object, $format = null, array $context = []): array
{
$normalized = $this->decorated->normalize($object, $format, $context);
uksort($normalized, function ($a, $b): int {
return $this->sort[$a] ?? INF <=> $this->sort[$b] ?? INF;
});
return $normalized;
}
public function supportsNormalization($data, $format = null): bool
{
return $this->supportsNormalization($data, $format);
}
} Usage : class BadlySorted
{
public $c = 3;
public $b = 2;
public $a = 1;
}
$normalizer = new ObjectNormalizer();
$normalizer->normalize(new BadlySorted()); // returns C B A
$sort = ['a', 'b', 'c'];
$normalizer = new SortedNormalizer($normalizer, $sort);
$normalizer->normalize(new BadlySorted()); // returns A B C |
Workaround for Serializing to CSVThe change introduced in #24256 makes it possible to define the order of the fields when serializing to CSV by passing the E.g.: $this->serializer->serialize(
[
'c' => 3,
'a' => 1,
'b' => 2
],
CsvEncoder::FORMAT,
[
CsvEncoder::HEADERS_KEY => ['a', 'b', 'c']
]); returns
PR for clarifying the documentation: symfony/symfony-docs#14609 |
One root cause of the issue is that PHP appends the parent properties after the child's ones (while e.g. C++ prepends the parent properties before the child's ones), which has bothered me in other contexts too 😠 For the Symfony serializer it seems that you can control the order by duplicating the parent properties and/or getters in the child (not a great solution...) |
Update: It looks like PHP 8.1 is going to change its order: https://github.com/php/php-src/blob/578b67da49af51b2f796a48782e51ceb62860943/UPGRADING#L334-L341
🙂 |
Thank you for this suggestion. |
Yes for shure!
Il giorno gio 21 apr 2022 alle ore 15:05 Carson: The Issue Bot <
***@***.***> ha scritto:
… Thank you for this suggestion.
There has not been a lot of activity here for a while. Would you still
like to see this feature?
—
Reply to this email directly, view it on GitHub
<#27441 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEWNSUNSN7C5FAPN6QJPJTVGFHALANCNFSM4FCS4TVA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Thank you for this suggestion. |
Just a quick reminder to make a comment on this. If I don't hear anything I'll close this. |
Yes, this feature would still make sense. |
Still makes sense. I serialized some objects that get exported to external systems (which are not really that modern...) and require the XML data to be in a certain order. |
Still relevant indeed. Just implemented an API where the XML requires an order. Could circumvent the problem by changing the order of the properties, but when extending a class from another and adding properties to it, the order is all wrong. |
Thank you for this suggestion. |
Yes. |
Yes. |
Most definitely yes. XML is still being used, and often it's validated against DTDs that specify exact tag order. |
most xml i work with, use that means i also can't move attributes into Traits because that would cause them to be discovered later and messed up the order |
I'm writing a service that sends invoices to the equivalent of the IRS (government level) and I just caught on that they use xsd:sequence everywhere. An order declaration would be really useful. |
it's been years. Imho "the order does not matter" is a bit indefensible python used that argument for their dict structures... they eventually made them ordered too by default. but I understand well that such a change would be a pain to implement. |
Anyone has a working workaround using attributes ? |
My workaround, just redefining the Example, a Trait that is used in other classes: trait MimeInfoTrait
{
/**
* @var Mime[]
*/
#[SerializedPath("[MIME_INFO][MIME]")]
protected array $mimes = [];
} example where it is used: class Product
{
/**
* @var PriceDetails[]
*/
#[SerializedName("PRODUCT_PRICE_DETAILS")]
protected array $priceDetails = [];
use MimeInfoTrait;
/**
* @var Mime[]
*/
#[SerializedPath("[MIME_INFO][MIME]")]
protected array $mimes = [];
#[SerializedName("USER_DEFINED_EXTENSIONS")]
protected array $extensions = [];
} Because I redefine the protected property |
Yes, we need this feature, especially for handling XML! Determining its usefulness is clear: the time spent implementing workarounds on all symfony projects justifies it. Plus, these workarounds might be hard for future developers to understand each time. A common workaround like rearranging properties or getter methods fails when properties and calculated fields coexist, as properties are prioritized, even when they have getters. As @dunglas mentioned, creating a dedicated 'sort' DTO to control the order and hydrate it from your Entity/BusinessDTO is more consistent. However, this approach is less convenient and harder to maintain compared to using a dedicated attribute. |
Thank you for this suggestion. |
Just a quick reminder to make a comment on this. If I don't hear anything I'll close this. |
Hey, I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen! |
Description
Currently, when normalizing, the serializer seems to order the properties in the same way it discovers them (guessing, reflection-based?). If possible, ot would make sense to use the order as supplied by the user.
Example
With classes:
with config:
I would expect it to come out normalized in the exact order specified in the mapping.
Instead, it comes out as:
The text was updated successfully, but these errors were encountered: