class-transformer icon indicating copy to clipboard operation
class-transformer copied to clipboard

feat: JSON property order

Open splincode opened this issue 5 years ago • 16 comments


@PropertyOrder([ 'id', 'password', 'email' ])
export class User {
   email: string;
   id: number;
   password: string;
}

plainToClass(User, jsonData) // User { id: 1, password: 'xxx', email: '[email protected]' }

splincode avatar Jul 11 '19 09:07 splincode

Or just preserving the order they are declared in. This is a real pain for me.

Logrythmik avatar Oct 10 '19 03:10 Logrythmik

Bump! .... Can we please look into this? I think this will be a great addition.

prateekkathal avatar Jul 15 '20 15:07 prateekkathal

Can you guys explain the reasoning behind this? Why is it important to be able to specify the property order on the object?

NoNameProvided avatar Jul 29 '20 22:07 NoNameProvided

@NoNameProvided The real reasoning I feel is readability in responses.

When using Class Transformers, the response changes into the order of

  1. Non-transformed properties
  2. Transformed properties

If we have another decorator defining the order or maybe just preserve the order the properties are declared in, it'll ensure higher response readability.

prateekkathal avatar Aug 03 '20 00:08 prateekkathal

@NoNameProvided Bump again.

prateekkathal avatar Nov 24 '20 21:11 prateekkathal

This is low prio for me, I still don't see the justification for this. If we can agree on some acceptable design someone can try to implement it, but first we need to agree on the API.

NoNameProvided avatar Feb 14 '21 15:02 NoNameProvided

+1 to caring about property order. And I have a concrete example:

I have server-side C# classes, and I'm using JSON.NET to move them back and forth over the wire.

Now, that library expects a $type property as the first property of a JSON object to be able to tell which class to deserialize into, particularly when it can't infer it from context. If I have to guess, it's because it's reading the JSON as a stream, and it needs to know which class to instantiate before taking in the rest of the object.

This is very similar to and ties neatly with class-transformer's discriminator. But currently CT's classToPlain is putting it at the end of the object, which I have to work around.

It's not exactly what this issue is about, but it's pretty close.

alexis-y avatar May 25 '21 22:05 alexis-y

+1 to caring about property order. And I have a concrete example:

I dump data into JSON files which are under git control. Since I started to use plainToInstance, the JSON files are modified only because of properties order. I would like the order to stay the same for speeding up diffing of modifications.

boutchitos avatar Jan 03 '22 20:01 boutchitos

@boutchitos Everyone has a different use-case tbh.

Typestack team - Will be great if we can get something done on this. I have to create customer transformer classes to fix this issue (see below). This is not ideal.

export class UserTransformer implements User {
  id!: string;
  firstName!: string;
  lastName: string;
  name: string;
  email!: string;
  
  @Expose({ groups: ['user.timestamps'] })
  createdAt: Date;

  @Expose({ groups: ['user.timestamps'] })
  updatedAt: Date;

  constructor(partial?: Partial<UserTransformer>) {
    const variablesToAssign = [
      'id',
      'firstName',
      'lastName',
      'name',
      'email',
      'createdAt',
      'updatedAt',
    ];

    variablesToAssign.forEach((variable) => {
      if (!partial[variable]) {
        partial[variable] = null;
      }

      Object.assign(this, { [variable]: partial[variable] });
    });
  }
}

I'd really appreciate if this is prioritized.

CC: @NoNameProvided

prateekkathal avatar Jan 10 '22 17:01 prateekkathal

+1 and I have another example: Private/public key signing. I need to sign my JSON payload with private key and verify the signature with public key. Thus I need to have a way to serialize my payload in a deterministic way. The order of fields should always be the same (in my case they should be sorted alphabetically).

dzikowski avatar Feb 18 '22 05:02 dzikowski

If it helps anyone: I found if I set excludeExtraneousValues = true in ClassTransformOptions and explicitly @ Expose everything, properties are output in the "correct" order. For my case at least, I think the disorder comes when the array of object keys is concatenated with the array of "exposed properties" in getKeys() in TransformOperationExecutor.js. So with excludeExtraneousValues = true, the object keys are skipped and just the @ Exposes are read in order. That said, I can see value in an @ Order property/method/accessor decorator that takes a single number (defaulting to 0) and is read in transform() or getKeys().

jrosenbl1 avatar May 15 '22 00:05 jrosenbl1

I'm also experiencing issues with the order. Especially when using the @Type decorator. When doing the plainToInstance transformation, the property with the @Type decorator is checked before the the discriminator property has been checked, therefore it couldn't find the right type to convert to:

Happens in this area of code: https://github.com/typestack/class-transformer/blob/develop/src/TransformOperationExecutor.ts#L232

yaronEcoPlant avatar Dec 20 '22 16:12 yaronEcoPlant

One way i found to bypass it is with the following code:

@IsEnum(DtoType)
  type: DtoType;

@Type(obj=> {
    switch (obj.object.type) {
      case DtoType.A:
        return TypeADto;
      case DtoType.B:
        return TypeBDto;
    }
  })
  attributes: TypeADto | TypeBDto

yaronEcoPlant avatar Dec 21 '22 07:12 yaronEcoPlant

i care about property order for a couple of reasons:

  1. readability: in nestjs projects, i use a PaginatedDTO class. if i have an api endpoint that sends paginated responses, i want an array value listed after a primitive value (e.g. { "count": 0, "limit": 20, "offset": 0, "data": [] }). similarly, if i have an endpoint that sends User data, id should be the first field shown when sending back a response
  2. preference: in most cases, i prefer fields to be listed alphabetically. in my case, this is also for readability 😊

unicornware avatar May 04 '23 08:05 unicornware

Something I've experienced with working with legacy providers over SOAP apis - for them the XML elements order matter, which is making working with them in TypeScript get tedious.

Working with dderevjanik/wsdl-tsclient we get interfaces, and we have to make sure that the JSON that we send have the order of propertoes match the order of properties in the interface.

Ideally - we could change the interfaces to classes and use plainToClass() to get the properties in the correct order as set in the class.

ZimGil avatar May 18 '23 14:05 ZimGil

I just ran into this issue as well.

One use case is if you are signing the JSON response with an cryptographic key for the purposes of e.g. non-repudiation, the order of the fields must be identical from what is signed versus ultimately sent to a client. In popular frameworks like Nest.js, it is common to use class-transformer on the boundary of an API for validation purposes. This then introduces the issue where a known field order needs to be maintained but class-transformer provides no way to influence the order.

Workarounds exist but perhaps if it were possible to simply pass our own sort function as a serializer option, then it could be trivially controlled with minimal API changes.

zaventh avatar Dec 01 '23 19:12 zaventh