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

feature: allow casting of primitive values during transformation #550

Open
NoNameProvided opened this issue Jan 14, 2021 · 29 comments
Open
Assignees
Labels
type: feature Issues related to new features.

Comments

@NoNameProvided
Copy link
Member

NoNameProvided commented Jan 14, 2021

Description

By design, class-transformer doesn't alter the value of properties when transforming an object into a class, but simply copies them over. However, there is an option (enableImplicitConversion) to change this behavior, when enabled the lib always attempts to cast the handled property value to the target property type. There are scenarios when some of the transformed properties should be casted in a non-standard way. The most common type of this is the casting stringified "true" and "false" to boolean. Currently, the library will cast both values to true, because Boolean("<any-non-zero-lenght-string>") will be always true.

There have been various discussions requesting to mark properties to be casted in a special way.

The most common scenario when this is required is using the @Query parameter in NestJS.

Proposed solution

There is two main way to approach this: adding decorators for each supported primitive type or enabling the feature at the transformation level via a new global transform option. Both have their pros and cons:

  • adding decorators makes it easier to customize the feature to the users need as they can choose to only transform specific properties with this new decorator
  • adding it as a global feature flag makes it easier to apply it at scale if someone needs this as the default behavior

Generally, I believe the decorator approach to be better and I propose the following decorators:

  • @CastToBoolean - transforms boolean, "true", "false", 0 and 1 to the appropriate boolean values
  • @CastToFloat - transforms a stringified number or number to the number representation, but not null or undefined
  • @CastToInteger - transforms a stringified number or number to an integer representation of the number, the number is rounded, but not null or undefined
  • @CastToString - transforms any received values to the string representation but not null or undefined
  • @CastToUndefined - transforms "undefined" to undefined
  • @CastToNull - transforms "null" to undefined

A single property can have multiple of these decorators applied.

Every one of these decorators accepts a single option:

@CastToBoolean({ force: boolean })

When set to true the decorator will always attempt to transform the received value (except when it's null or undefined). However, when it is false (the default value) it will only attempt to transform values when it makes sense. For example:

  • for booleans it will only transform "true", "false", 0, 1 and skip the transformation if any other value is received
  • for numbers it will only transform if the value can be parsed as a float number (aka: "not-number" will be skipped)

The transformation must take place before the implicit conversion of the value (if enabled). When enableImplicitConversion is enabled both transformations will run on the given property.

Please do not comment non-related stuff like +1 or waiting for this. If you want to express interest you can like the issue.

@DanielLoyAugmedix
Copy link

Did this get worked on? I'd like to help if this is still pending.

@NoNameProvided NoNameProvided removed the flag: needs discussion Issues which needs discussion before implementation. label Feb 22, 2021
@NoNameProvided
Copy link
Member Author

Not yet, feel free to pick it up! Do you have any change request for the proposal or do you want to take a crack at it in it's current form?

PS: If you made progress and I seem to be unavailable for more than a few days, feel free to ping me on Twitter. (I have 100+ notifications on Github, so some of them gets lost sometimes)

@davidakerr
Copy link

davidakerr commented Mar 18, 2021

This is how I got round the issue while managing to keep the boolean typing.

import { Transform } from 'class-transformer';

const ToBoolean = () => {
  const toPlain = Transform(
    ({ value }) => {
      return value;
    },
    {
      toPlainOnly: true,
    }
  );
  const toClass = (target: any, key: string) => {
    return Transform(
      ({ obj }) => {
        return valueToBoolean(obj[key]);
      },
      {
        toClassOnly: true,
      }
    )(target, key);
  };
  return function (target: any, key: string) {
    toPlain(target, key);
    toClass(target, key);
  };
};

const valueToBoolean = (value: any) => {
  if (typeof value === 'boolean') {
    return value;
  }
  if (['true', 'on', 'yes', '1'].includes(value.toLowerCase())) {
    return true;
  }
  if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
    return false;
  }
  return undefined;
};

export { ToBoolean };
export class SomeClass {
  @ToBoolean()
  isSomething : boolean;
}

@csulit
Copy link

csulit commented Apr 2, 2021

Any update on this?

@softmarshmallow
Copy link

softmarshmallow commented May 25, 2021

Yeah, all great and needed to be done while a go.

When will this be released?

@Goldziher
Copy link

+1

@little-thing
Copy link

Is there a way now

@bodolawale
Copy link

Still waiting on this btw

@vlad-ti
Copy link

vlad-ti commented Sep 7, 2021

+1

3 similar comments
@kasvith
Copy link

kasvith commented Sep 28, 2021

+1

@well-balanced
Copy link

+1

@elmirmahmudev
Copy link

+1

@klub-ayush
Copy link

+1 W E N E E D I T !

@andkom
Copy link

andkom commented Feb 9, 2022

also +1

@vhorin-mp
Copy link

+1

@LuisOfCourse
Copy link

Is there a way now

Still the best option is using Boolean("").

@swpark6
Copy link

swpark6 commented Mar 17, 2022

+1

1 similar comment
@marco-ippolito
Copy link

+1

@yitzchak-ben-ezra-ecoplant

it won't help you guys...
this repo is not maintained anymore..
you can copy the code above to your project and use it...
I don't see any other solution

@yitzchak-ben-ezra-ecoplant
Copy link

yitzchak-ben-ezra-ecoplant commented Apr 4, 2022

this works for us.. doesn't cover all cases - but it's good enough for our needs:

  @IsOptional()
  @Transform(({ value }) => value?.toLowerCase() === 'true')
  @IsBoolean()
  someField: boolean;

@yuri-snke
Copy link

Any news on this case?

@DanielMaranhao
Copy link

I'm currently using David Kerr solution. But any news?

@tatianajiselle
Copy link

tatianajiselle commented Feb 1, 2023

All the solutions ive seen online for transforming this bug havent been working since the request param im expecting is a boolean not a string. So I casted the type to a string with transformer and ran the following, which works perfectly for my use case. Maybe it will help someone else.

@Type(() => String)
@Transform(({ value }) => {
    if (value === 'true') {
      return true;
    } else {
      return false;
    }
  })
  public isBoolean?: boolean;

@ismoiliy98
Copy link

All the solutions ive seen online for transforming this bug havent been working since the request param im expecting is a boolean not a string. So I casted the type to a string with transformer and ran the following, which works perfectly for my use case. Maybe it will help someone else.

@Type(() => String)
@Transform(({ value }) => {
    if (value === 'true') {
      return true;
    } else {
      return false;
    }
  })
  public isBoolean?: boolean;

Thank you!!! You saved my life ♥️

@IuliiaBondarieva
Copy link

Any news on this case?

@sandratatarevicova
Copy link

Hi @NoNameProvided,

why do you prefer the decorator approach? For our use case (parsing requests in NestJS), the global flag would be much better because boolean values would not require any special decorators and would be correctly transformed by default as other types. With the decorator approach, I think that developers would often forget about this, which would lead to bugs.

Before I found this issue, I created PR #1686 (it introduces a new global flag to enable the boolean conversion).

@0x0bit
Copy link

0x0bit commented Aug 8, 2024

@NoNameProvided Why are you so stubborn, which is a very common situation, why do you just not accept the opinions of others, on the one hand say listen to the community, on the other hand stubborn do not solve the problem?

1 similar comment
@Mubashir-Qureshi
Copy link

@NoNameProvided Why are you so stubborn, which is a very common situation, why do you just not accept the opinions of others, on the one hand say listen to the community, on the other hand stubborn do not solve the problem?

@devCTS
Copy link

devCTS commented Aug 29, 2024

I did this in my Dto
It worked for me. (required only boolean value, values can be optional)
Hope that help

@IsOptional()
  @IsBoolean({ message: 'Value must be boolean' })
  @Transform((value) => {
    value.options.enableImplicitConversion = false;
    return value.obj.incoming_status;
  })
  outgoing_status: boolean;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature Issues related to new features.
Development

No branches or pull requests