Skip to content
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

plainToClass parses object to array with properties #255

Closed
baflo opened this issue May 9, 2019 · 13 comments
Closed

plainToClass parses object to array with properties #255

baflo opened this issue May 9, 2019 · 13 comments
Labels
status: cannot reproduce Bug reports which cannot be reproduced. type: fix Issues describing a broken feature.

Comments

@baflo
Copy link

baflo commented May 9, 2019

I can't really tell what's happening and how, but since changing from 0.2.0 to 0.2.2 we get an error, that nested objects are parsed to arrays. Something like this:

const entity = plainToClass(Users, { users: [{ id: 123, name: "Carl"}] });
console.log(entity); // { user: [[ id: 123, name: "Carl ]] }
@NoNameProvided
Copy link
Member

NoNameProvided commented May 9, 2019

Hi!

Can you please share the Users and the User class to have a working minimal re-producible snippet?

Can you check if it's working in 0.2.1? I think this was introduced in #191 (what was merged in 0.2.2.

@NoNameProvided
Copy link
Member

I have tried to reproduce it with the following code, but it worked for me correctly, so please do share a minimal reproduction. Thanks!

import 'reflect-metadata';
import { plainToClass, Transform, Expose, Type, Exclude } from 'class-transformer';

class User {

  id: number;

  name: string;
  
}

class Users {

  @Type(() => User)
  users: User[];
}

const instance = plainToClass(Users, { users: [{ id: 123, name: "Carl"}] });
console.log(instance)  // corretly logs { users: [{ id: 123, name: "Carl"}] }
    

@NoNameProvided NoNameProvided added the status: needs triage Issues which needs to be reproduced to be verified report. label May 9, 2019
@eyexpo-isaac
Copy link

I got a similar issue after upgrading to 0.2.2,
My code that used plainToClass start to complain
TypeError: Reflect.getMetadata is not a function
(I tested it in 0.2.1 and it was working fine with no error)

By import 'reflect-metadata'; at the file top resolved that issue.

@baflo
Copy link
Author

baflo commented May 10, 2019

I tried to reproduce the error with some snippets extracted from our production code, but couldn't reproduce either, yet. I have to give it another try the next days.

@NoNameProvided
Copy link
Member

I have to give it another try the next days.

Thanks, I am waiting for it.

I got a similar issue after upgrading to 0.2.2

@baflo I see your issue at MichalLytek/class-transformer-validator#20 but I cannot reproduce this issue with that example code either. (Stackblitz Demo)

In the console, I see an instance of User with the correct value az expected:

{  
  identifier: "johndoe@gmail.com", 
  password: "password123"
}

@eyexpo-isaac
Copy link

I have to give it another try the next days.

Thanks, I am waiting for it.

I got a similar issue after upgrading to 0.2.2

@baflo I see your issue at 19majkel94/class-transformer-validator#20 but I cannot reproduce this issue with that example code either. (Stackblitz Demo)

In the console, I see an instance of User with the correct value az expected:

{  
  identifier: "johndoe@gmail.com", 
  password: "password123"
}

Thank you for testing it out.

My question is,
After v0.2.2, you have to import 'reflect-metadata';
I remove the import from your demo. In the console, You will see an error TypeError: Reflect.getMetadata is not a function: (Demo)

But before v0.2.2, like v0.2.1, the same code could run with no error without import 'reflect-metadata';
I remove the import and install v0.2.1 from your demo. In the console, You could see an instance of User with the correct value as expected: (Demo)

@NoNameProvided
Copy link
Member

After v0.2.2, you have to import 'reflect-metadata';

You had to import reflect-metadata before. Does your code work when reflect-metadata is included at the first line of your application?

I remove the import from your demo. In the console, You will see an error TypeError: Reflect.getMetadata is not a function

Now we are directly requesting type information generated by Typescript thats why you receive that error.

I remove the import and install v0.2.1 from your demo. In the console, You could see an instance of User with the correct value as expected

I don't understand what you want to say with this. We knew the old version transformed correctly. The question is about an invalid transform with 0.2.2. So please provide a demo with the 0.2.2 version what demonstrates the invalid behaviour.

@baflo
Copy link
Author

baflo commented May 22, 2019

I finally had the time to investigate the problem and comprehend an example. I found that I actually can fix it (as I had an issue), but still the behaviour is odd:

import { plainToClass, Type } from "class-transformer";
import { IsString, Validate, ValidatorConstraintInterface } from "class-validator";

class WrapperValidator implements ValidatorConstraintInterface {
  public async validate(): Promise<boolean> {
    return true;
  }
}

describe("class-transformer.plainToClass", function() {
  beforeEach(async function() {
    class Wrapper {
      @Validate(WrapperValidator)
      @Type(() => Data) // This was missing, i.e. the odd behaviour occurs, if I remove this
      public elements!: any[];

      constructor(elements: any[]) {
        this.elements = elements;
      }
    }

    class Data {
      @IsString()
      public data!: string;

      constructor(data: string) {
        this.data = data;
      }
    }

    this.event = JSON.parse(JSON.stringify(new Wrapper([new Data("Test")])));
    this.class = plainToClass(Wrapper, this.event) as any;
  });

  fit("creates a correct class object", async function() {
    expect(JSON.stringify(this.class)).toEqual(JSON.stringify(this.event));
  });
});

So, if I remove the @Type() decorator, plainToClass generates an array object for each element of elements, although they should be of object type.

@JAspeling
Copy link

JAspeling commented Oct 17, 2020

Will this work:

export type Users = User[];

export class User {
    id: number;
    name: string;
}

and then casting an object to an array:

    ...
    usersObj: any = response ; // object typed user[], matches the type `Users`
    const strongTyped = plainToClass(Users, usersObj); // 'Users' only refers to a type, but is being used as a value here
    ...

While this does not work, what is the best way to achieve this? Would I have to implement it like below?

    usersObj.map(obj => plainToClass(User, obj));

@mrnateriver
Copy link

Came across the same issue.

class-transformer@^0.3.1:
  version "0.3.1"
  integrity sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==

It seems to happen when an array of objects is not annotated with specific type (like workarounds here suggested).

The error, as I see it, happens here:

finalValue = this.transform(subSource, subValue, type, arrayType, isSubValueMap, level + 1);

this.transform is called recursively with type === Array. In recursive call, another branch is used:
realTargetType = targetType;
}
const value = this.transform(
subSource,
subValue,
realTargetType,
undefined,
subValue instanceof Map,
level + 1
);

And since on this iteration targetType was already an Array, it recurses further for deserialization of entries with the same Array type, when actually it had to guess the type from the scratch.

I should also point out that I'm using enableImplicitConversion === true, but it doesn't seem to make a difference.

@Sreeramm
Copy link

Sreeramm commented Jan 27, 2021

Will this work:

export type Users = User[];

export class User {
    id: number;
    name: string;
}

and then casting an object to an array:

    ...
    usersObj: any = response ; // object typed user[], matches the type `Users`
    const strongTyped = plainToClass(Users, usersObj); // 'Users' only refers to a type, but is being used as a value here
    ...

While this does not work, what is the best way to achieve this? Would I have to implement it like below?

    usersObj.map(obj => plainToClass(User, obj));

@JAspeling You got any other solving to resolve this issue ?

@NoNameProvided
Copy link
Member

Closing this as I still don't see a reproducible error. Everything works as expected.

@baflo In your example, you have to specify @Type decorator to make it work because:

  • if no decorator specified, TS won't emit type information
  • for arrays the Array is emitted as type information, not the contained class, so you need to explicitly specify the type
  • to auto guess the type currently you have to use enableImplicitConversion but that will convert both primitive and class types to the target value, so that is maybe not that you need

This is the expected behavior.

@NoNameProvided NoNameProvided added status: cannot reproduce Bug reports which cannot be reproduced. and removed status: needs triage Issues which needs to be reproduced to be verified report. labels Feb 15, 2021
@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 18, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
status: cannot reproduce Bug reports which cannot be reproduced. type: fix Issues describing a broken feature.
Development

No branches or pull requests

6 participants