diff --git a/src/TransformOperationExecutor.ts b/src/TransformOperationExecutor.ts index 0533f03df..4de905ea2 100644 --- a/src/TransformOperationExecutor.ts +++ b/src/TransformOperationExecutor.ts @@ -1,7 +1,7 @@ import { defaultMetadataStorage } from './storage'; import { ClassTransformOptions, TypeHelpOptions, TypeMetadata, TypeOptions } from './interfaces'; import { TransformationType } from './enums'; -import { getGlobal, isPromise } from './utils'; +import { getGlobal, isPromise, toBoolean } from './utils'; function instantiateArrayType(arrayType: Function): Array | Set { const array = new (arrayType as any)(); @@ -106,8 +106,9 @@ export class TransformOperationExecutor { if (value === null || value === undefined) return value; return Number(value); } else if (targetType === Boolean && !isMap) { - if (value === null || value === undefined) return value; - return Boolean(value); + // Note: Next line can safely be removed as this is being handled by util to return false if either of these two are set. + //if (value === null || value === undefined) return value; + return toBoolean(value); } else if ((targetType === Date || value instanceof Date) && !isMap) { if (value instanceof Date) { return new Date(value.valueOf()); diff --git a/src/utils/index.ts b/src/utils/index.ts index 382ad2d7f..184a32597 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './get-global.util'; export * from './is-promise.util'; +export * from './to-boolean.util'; diff --git a/src/utils/to-boolean.util.ts b/src/utils/to-boolean.util.ts new file mode 100644 index 000000000..1f93240a3 --- /dev/null +++ b/src/utils/to-boolean.util.ts @@ -0,0 +1,29 @@ +export function toBoolean(value: any): value is Boolean { + if (value === undefined || value === null) { + return false; + } + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'number') { + return value > 0; + } + if (typeof value === 'string') { + value = value.toLowerCase(); + switch (value) { + case 'true': + case 'yes': + case 'on': + case '1': + return true; + case 'false': + case 'no': + case 'off': + case '0': + return false; + default: + return false; + } + } + return false; +} diff --git a/test/functional/basic-functionality.spec.ts b/test/functional/basic-functionality.spec.ts index 9705b405d..84c52c5e4 100644 --- a/test/functional/basic-functionality.spec.ts +++ b/test/functional/basic-functionality.spec.ts @@ -626,7 +626,7 @@ describe('basic functionality', () => { uuidBuffer: uuid, nullableString: null, nullableNumber: null, - nullableBoolean: null, + nullableBoolean: false, nullableDate: null, nullableBuffer: null, }); @@ -646,7 +646,7 @@ describe('basic functionality', () => { uuidBuffer: uuid, nullableString: null, nullableNumber: null, - nullableBoolean: null, + nullableBoolean: false, nullableDate: null, nullableBuffer: null, }); @@ -664,7 +664,7 @@ describe('basic functionality', () => { uuidBuffer: uuid, nullableString: null, nullableNumber: null, - nullableBoolean: null, + nullableBoolean: false, nullableDate: null, nullableBuffer: null, }); @@ -682,7 +682,7 @@ describe('basic functionality', () => { uuidBuffer: uuid, nullableString: null, nullableNumber: null, - nullableBoolean: null, + nullableBoolean: false, nullableDate: null, nullableBuffer: null, }); @@ -700,7 +700,7 @@ describe('basic functionality', () => { uuidBuffer: uuid, nullableString: null, nullableNumber: null, - nullableBoolean: null, + nullableBoolean: false, nullableDate: null, nullableBuffer: null, }); @@ -720,7 +720,7 @@ describe('basic functionality', () => { uuidBuffer: uuid, nullableString: null, nullableNumber: null, - nullableBoolean: null, + nullableBoolean: false, nullableDate: null, nullableBuffer: null, }); diff --git a/test/functional/implicit-type-declarations.spec.ts b/test/functional/implicit-type-declarations.spec.ts index 889565512..008ab1792 100644 --- a/test/functional/implicit-type-declarations.spec.ts +++ b/test/functional/implicit-type-declarations.spec.ts @@ -142,3 +142,99 @@ describe('plainToInstance transforms built-in primitive types properly', () => { expect(result.boolean2).toBeFalsy(); }); }); + +describe('plainToInstance transforms boolean types properly', () => { + defaultMetadataStorage.clear(); + + class Example { + @Type() + undefinedValue: boolean; + + @Type() + nullValue: boolean; + + @Type() + booleanTrueString: boolean; + + @Type() + booleanFalseString: boolean; + + @Type() + trueNumberStringValue: boolean; + + @Type() + falseNumberStringValue: boolean; + + @Type() + boolean: boolean; + + @Type() + boolean2: boolean; + + @Type() + boolean3: boolean; + + @Type() + boolean4: boolean; + + @Type() + onTrueString: boolean; + + @Type() + offFalseString: boolean; + + @Type() + booleanTrueNumber: boolean; + + @Type() + booleanFalseNumber: boolean; + + @Type() + falseRandomString: boolean; + } + + const result: Example = plainToInstance( + Example, + { + undefinedValue: undefined, + nullValue: null, + booleanTrueString: 'true', + booleanFalseString: 'false', + trueNumberStringValue: '1', + falseNumberStringValue: '0', + boolean: 1, + boolean2: 0, + boolean3: true, + boolean4: false, + onTrueString: 'on', + offFalseString: 'off', + booleanTrueNumber: '1', + booleanFalseNumber: '0', + falseRandomString: 'some random value that needs to be false', + }, + { enableImplicitConversion: true } + ); + it('should recognize and convert "undefined" and "null" to false', () => { + expect(result.undefinedValue).toBeFalsy(); + expect(result.nullValue).toBeFalsy(); + }); + + it('should recognize and convert string to boolean', () => { + expect(result.booleanTrueString).toBeTruthy(); + expect(result.booleanFalseString).toBeFalsy(); + expect(result.trueNumberStringValue).toBeTruthy(); + expect(result.falseNumberStringValue).toBeFalsy(); + expect(result.onTrueString).toBeTruthy(); + expect(result.offFalseString).toBeFalsy(); + expect(result.booleanTrueNumber).toBeTruthy(); + expect(result.booleanFalseNumber).toBeFalsy(); + expect(result.falseRandomString).toBeFalsy(); + }); + + it('should recognize and convert to boolean', () => { + expect(result.boolean).toBeTruthy(); + expect(result.boolean2).toBeFalsy(); + expect(result.boolean3).toBeTruthy(); + expect(result.boolean4).toBeFalsy(); + }); +});