From 618d1705b632d91ac727117f4f02477c761919ed Mon Sep 17 00:00:00 2001 From: EinfachHans Date: Thu, 2 Nov 2023 08:45:00 +0100 Subject: [PATCH 1/3] fix: validation returns resulted data Closes: #2497 --- .../src/pipes/ValidationPipe.ts | 2 +- packages/specs/ajv/src/services/AjvService.ts | 9 +- yarn.lock | 163 ------------------ 3 files changed, 9 insertions(+), 165 deletions(-) diff --git a/packages/platform/platform-params/src/pipes/ValidationPipe.ts b/packages/platform/platform-params/src/pipes/ValidationPipe.ts index be0b0a9cd4c..9a5275fe40d 100644 --- a/packages/platform/platform-params/src/pipes/ValidationPipe.ts +++ b/packages/platform/platform-params/src/pipes/ValidationPipe.ts @@ -72,7 +72,7 @@ export class ValidationPipe implements PipeMethods { customKeys: true }); - await this.validator.validate(value, { + value = await this.validator.validate(value, { schema, type: metadata.isClass ? metadata.type : undefined, collectionType: metadata.collectionType diff --git a/packages/specs/ajv/src/services/AjvService.ts b/packages/specs/ajv/src/services/AjvService.ts index 3e56f4c981c..8e94fda2a23 100644 --- a/packages/specs/ajv/src/services/AjvService.ts +++ b/packages/specs/ajv/src/services/AjvService.ts @@ -21,6 +21,9 @@ export class AjvService { @Constant("ajv.errorFormatter", defaultErrorFormatter) protected errorFormatter: ErrorFormatter; + @Constant("ajv.returnsCoercedValues") + protected returnsCoercedValues: boolean; + @Inject() protected ajv: Ajv; @@ -30,7 +33,7 @@ export class AjvService { const schema = defaultSchema || getJsonSchema(type, {...additionalOptions, customKeys: true}); if (schema) { - const localValue = deepClone(value); + const localValue = this.returnsCoercedValues ? value : deepClone(value); const validate = this.ajv.compile(schema); const valid = await validate(localValue); @@ -44,6 +47,10 @@ export class AjvService { value: localValue }); } + + if (this.returnsCoercedValues) { + return localValue; + } } return value; diff --git a/yarn.lock b/yarn.lock index 6111da776d2..79079e5d360 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5614,70 +5614,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== -"@tsed/common@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/common/-/common-7.41.2.tgz#504c831e7c45c763f450de312f18b7b15cdbb863" - integrity sha512-pNWC0695BmxTr48XZPXOkokmtp/i60rgk5wu9rMoZ9KxHmWSpmElornoDeZdpG/OT4b2+eCsWhsOxnOzDI0kbA== - dependencies: - "@tsed/core" "7.41.2" - "@tsed/di" "7.41.2" - "@tsed/exceptions" "7.41.2" - "@tsed/json-mapper" "7.41.2" - "@tsed/logger" ">=6.2.2" - "@tsed/logger-file" ">=6.2.2" - "@tsed/platform-exceptions" "7.41.2" - "@tsed/platform-log-middleware" "7.41.2" - "@tsed/platform-middlewares" "7.41.2" - "@tsed/platform-params" "7.41.2" - "@tsed/platform-response-filter" "7.41.2" - "@tsed/platform-router" "7.41.2" - "@tsed/platform-views" "7.41.2" - "@tsed/schema" "7.41.2" - "@types/json-schema" "7.0.11" - accepts "^1.3.8" - tslib "2.5.0" - uuid "8.3.2" - -"@tsed/core@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/core/-/core-7.41.2.tgz#bd86aa87d843c8f8df73dcbe4326a3502f0c7d21" - integrity sha512-dDzJ8SsG5V8bjNQugWGYUV5hWvjfD4bXnTv3s5dq787I744oPA68KluDA6/X0FYTzeA/HBIvadH9scpGXmHR/A== - dependencies: - reflect-metadata "^0.1.13" - tslib "2.5.0" - -"@tsed/di@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/di/-/di-7.41.2.tgz#932c25243b50b263662dd9be1541dcc58e4bf53e" - integrity sha512-FhfU3TdN/2Y9pl0VGtsS99jLHbG4WPvM2Oo2i6tC7MRW8HhhlVo4GS4T31SgX6ioMtE/GwkhPM3I2MhAitMwJw== - dependencies: - tslib "2.5.0" - -"@tsed/engines@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/engines/-/engines-7.41.2.tgz#d058b402accf002d5581692b3e7c746bcd2aa878" - integrity sha512-rN8FLoifaVTGUyZ/7KfGwSSQT2DAutWEYxjPY/5rsReM+YUctNFg1xERVfcpklrfJ8E8L1hPhXv69t45SB9RyQ== - dependencies: - filedirname "^2.7.0" - fs-extra "11.1.1" - tslib "2.5.0" - -"@tsed/exceptions@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/exceptions/-/exceptions-7.41.2.tgz#d5f4053b36952eca1c809a246ed1120628b8d394" - integrity sha512-dg/lRvvnYUoHdmDA6rC4lxeZ0oUZ48y/TwSBaxlpnhbn+NN5Ock2mwFbRsd0BAY4gpD5d+a8Ivc0jUuK4Gz9lQ== - dependencies: - change-case "4.1.2" - statuses ">=2.0.1" - tslib "2.5.0" - -"@tsed/json-mapper@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/json-mapper/-/json-mapper-7.41.2.tgz#90ad45daed362650a80fc54110247f73649b122e" - integrity sha512-j8ijpplRVHlRn76hcbZ3RsxAtob8W1+OMWnpZgXpzeES8Xi2HQiE9WM4YNYqsfEYH8IRQhCYXfuKnN+hOImBpg== - dependencies: - tslib "2.5.0" - "@tsed/logger-file@>=6.2.2": version "6.2.2" resolved "https://registry.yarnpkg.com/@tsed/logger-file/-/logger-file-6.2.2.tgz#ffd1f9ab1a0e09160433676976c370a0800ba674" @@ -5717,105 +5653,6 @@ normalize-path "3.0.0" semver "7.3.7" -"@tsed/normalize-path@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/normalize-path/-/normalize-path-7.41.2.tgz#d907d1c5156b73509ddf55d7cc63d0b285529621" - integrity sha512-Xo8iOY2u9qu7iT8ebJEerFrlBm9CyNAKVt5TrEFWaC4W5NmglKmCaONLjadLXxto4i0lblUYIVn/B42VzTBFxg== - dependencies: - normalize-path "3.0.0" - tslib "2.5.0" - -"@tsed/openspec@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/openspec/-/openspec-7.41.2.tgz#17038aeae9380e98d1fd3951121b4594f39de846" - integrity sha512-FfokQGNukW1w+ECmtKQ1V65+2LRHi7yM9xPOLAHS1Ew3rwa0qkJkNnuSD0ORJzTI4oozBvB0m2E9EKUWWrCFeA== - -"@tsed/platform-exceptions@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-exceptions/-/platform-exceptions-7.41.2.tgz#29dbb1c0d9d1bdd3d822fb2ba41cccb8c3875227" - integrity sha512-1mNpDX3lrlA7WkUHYtSyfD8tMiMNoVEOwh1lSF7veFXQDGDjZoA3PZdIYBlBQhI1FAjV0fUJ1tU9saMo4L1YLA== - dependencies: - tslib "2.5.0" - -"@tsed/platform-express@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-express/-/platform-express-7.41.2.tgz#c720fa3d04f9d1a85cf475b05e83685b0420718c" - integrity sha512-3aj7X3wxti0L6IbSPeys7gxjBsa/u8apvDsCst5kB4vVWQRXO1G8mnHldZhEpZmp9b5Zy9YAbG/BPnW5o1GWow== - dependencies: - express "^4.18.1" - multer "^1.4.5-lts.1" - tslib "2.5.0" - -"@tsed/platform-log-middleware@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-log-middleware/-/platform-log-middleware-7.41.2.tgz#85c56297bf228c2c08a585f489467e2d14ca49af" - integrity sha512-MFz04tS0WjzdKydWnus6qREClRje5nIs9aSqsCVcuWEwubY+0Qcl1wgzAqQdsOxGIXybY9A3Sk8rympCVhD+Tg== - dependencies: - tslib "2.5.0" - -"@tsed/platform-middlewares@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-middlewares/-/platform-middlewares-7.41.2.tgz#4ae96db83209653f481091a10b78c1bc521b73fe" - integrity sha512-PgUvAjRQ13kh6WIKLMdUvsfaKZyPo13QQnHu68Xp48nODCxZ3CzeXYu/jBV/2wQIuZpZZrrawmqnujbGA8eXIg== - dependencies: - tslib "2.5.0" - -"@tsed/platform-params@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-params/-/platform-params-7.41.2.tgz#a3f459165f15eae69ffb7f49c38f179dc8b9c15d" - integrity sha512-O00YUCTJTrxyR2+VVkbojpeP1PVQpLkFhc/KPonOJXgu9e2ftCGQ38vXShMQJwLQfwpBEZtdjuf8KpnVpSGNnA== - dependencies: - tslib "2.5.0" - -"@tsed/platform-response-filter@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-response-filter/-/platform-response-filter-7.41.2.tgz#fa0f30780adb14799a65381d77584c745611b770" - integrity sha512-EGAWcaPJ3aA73U6P7hK3QMInGwGFfcolJMOjtCLSR8ZKLp30j/SM1D94cLGZTqdoBFQXi2smGRKoCUpBjR5enA== - dependencies: - tslib "2.5.0" - -"@tsed/platform-router@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-router/-/platform-router-7.41.2.tgz#990d10a7145ced53fc0e45f8235876617f478a20" - integrity sha512-w0NQeBTqy2x1T2AMPoYZL/0khyHDYansARAyBg6QVI2j75VJUWRMC/VJutJWedUuiFd4QZX365JFwH6s0gs9aQ== - dependencies: - tslib "2.5.0" - -"@tsed/platform-views@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/platform-views/-/platform-views-7.41.2.tgz#df3a984a8577ad5ca8b4e42f76438f25cf1f6b77" - integrity sha512-45NNJ9n2OplW6LD1sfCxnyMgPO6l2ojABwjRNkz2Myy1DaPIc3S/BJnZm+jXJQ6xHZUxuLMepZgvmO4t+0c4zQ== - dependencies: - "@tsed/engines" "7.41.2" - ejs "^3.1.5" - tslib "2.5.0" - -"@tsed/schema@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/schema/-/schema-7.41.2.tgz#a08c1b1bfb2aeda7584bee7b856a74fb9564e457" - integrity sha512-eOfKb+wbe+D+Bi1sS+5HwpQEBBxYc9ahLrE7fjZh7l1vziWo4hf0t6FA4g4t/hZlq1QAQ1PixwddvJQ4ieMi6w== - dependencies: - "@tsed/openspec" "7.41.2" - change-case "^4.1.2" - fs-extra "^11.1.1" - json-schema "0.4.0" - picomatch "2.3.1" - statuses ">=2.0.1" - tslib "2.5.0" - -"@tsed/swagger@7.41.2": - version "7.41.2" - resolved "https://registry.yarnpkg.com/@tsed/swagger/-/swagger-7.41.2.tgz#dbe8b2024449d638600bd70cf6babecaab8222e1" - integrity sha512-6s9661crHSXyGeuzo5QahsGCGrWc+rKT/ENRYdpqa5tZC8FzFp6VKJMyKurYrXXzhV8gvSodbOmD6zLhiVztZQ== - dependencies: - "@tsed/normalize-path" "7.41.2" - "@tsed/openspec" "7.41.2" - filedirname "^2.7.0" - fs-extra "11.1.1" - micromatch "4.0.5" - swagger-ui-dist "^4.5.2" - tslib "2.5.0" - "@tsed/ts-doc@4.0.14": version "4.0.14" resolved "https://registry.yarnpkg.com/@tsed/ts-doc/-/ts-doc-4.0.14.tgz#6f09a1b09962354c0a1e7847f475a3e35a191eed" From 63af7b462d2d81d70be3ea6a9f4e6c619f8db549 Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Thu, 2 Nov 2023 13:26:45 +0100 Subject: [PATCH 2/3] feat(ajv): add returnsCoercedValues option to keep the coerced value by ajv Closes: #2355 --- .../specs/ajv/src/interfaces/IAjvSettings.ts | 1 + .../specs/ajv/src/services/AjvService.spec.ts | 1 + .../integration/nullable.integration.spec.ts | 187 ++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 packages/specs/ajv/test/integration/nullable.integration.spec.ts diff --git a/packages/specs/ajv/src/interfaces/IAjvSettings.ts b/packages/specs/ajv/src/interfaces/IAjvSettings.ts index c9b3969b199..c10951942d3 100644 --- a/packages/specs/ajv/src/interfaces/IAjvSettings.ts +++ b/packages/specs/ajv/src/interfaces/IAjvSettings.ts @@ -19,4 +19,5 @@ export type ErrorFormatter = (error: AjvErrorObject) => string; export interface IAjvSettings extends Options { Ajv?: any; errorFormatter?: ErrorFormatter; + returnsCoercedValues?: boolean; } diff --git a/packages/specs/ajv/src/services/AjvService.spec.ts b/packages/specs/ajv/src/services/AjvService.spec.ts index 55d56a69c6e..0d4351c1112 100644 --- a/packages/specs/ajv/src/services/AjvService.spec.ts +++ b/packages/specs/ajv/src/services/AjvService.spec.ts @@ -6,6 +6,7 @@ import {AjvService} from "./AjvService"; describe("AjvService", () => { beforeEach(() => PlatformTest.create()); afterEach(() => PlatformTest.reset()); + it("should use the function api as schema", async () => { const ajvService = PlatformTest.get(AjvService); diff --git a/packages/specs/ajv/test/integration/nullable.integration.spec.ts b/packages/specs/ajv/test/integration/nullable.integration.spec.ts new file mode 100644 index 00000000000..2f316199cbf --- /dev/null +++ b/packages/specs/ajv/test/integration/nullable.integration.spec.ts @@ -0,0 +1,187 @@ +import "../../src/index"; +import {BodyParams, ParamTypes, ParamValidationError, PlatformTest, Post, QueryParams, UseParam, ValidationPipe} from "@tsed/common"; +import {BadRequest} from "@tsed/exceptions"; +import {CollectionOf, getJsonSchema, JsonParameterStore, MinLength, Nullable, Property, Required, Schema, string} from "@tsed/schema"; + +async function validate(value: any, metadata: any) { + const pipe: ValidationPipe = await PlatformTest.invoke(ValidationPipe); + + try { + return await pipe.transform(value, metadata); + } catch (er) { + if (er instanceof BadRequest) { + return ParamValidationError.from(metadata, er); + } + + throw er; + } +} + +class NestedModel { + @Property() + id: string; +} + +class NullModel { + @Nullable(String) + prop1: string | null; + + @Nullable(Number) + prop2: number; + + @Nullable(Date) + prop3: Date | null; + + @Nullable(NestedModel) + prop4: NestedModel | null; + + @Nullable(Array) + prop5: string[] | null; +} + +class NoNullableModel { + @Property() + prop1: string; + + @Property() + prop2: number; + + @Property() + prop3: Date; + + @Property() + prop4: NestedModel; + + @CollectionOf(String) + prop5: string[]; +} + +describe("Nullable model", () => { + describe("when returnsCoercedValues is false", () => { + beforeEach(() => + PlatformTest.create({ + ajv: { + coerceTypes: "array" + } + }) + ); + afterEach(() => PlatformTest.reset()); + it("should validate object and returns the original value (NullModel)", async () => { + class Ctrl { + get(@BodyParams() value: NullModel) {} + } + + const value = { + prop1: null, + prop2: null, + prop3: null, + prop4: null, + prop5: null + }; + + const result = await validate(value, JsonParameterStore.get(Ctrl, "get", 0)); + + expect(result).toEqual({ + prop1: null, + prop2: null, + prop3: null, + prop4: null, + prop5: null + }); + + const result2 = await validate( + { + prop1: "test", + prop2: 1, + prop3: "2020-01-01", + prop4: { + id: "id" + }, + prop5: ["test"] + }, + JsonParameterStore.get(Ctrl, "get", 0) + ); + + expect(result2).toEqual({ + prop1: "test", + prop2: 1, + prop3: "2020-01-01", + prop4: { + id: "id" + }, + prop5: ["test"] + }); + }); + it("should validate object and returns the original value (NoNullableModel)", async () => { + class Ctrl { + get(@BodyParams() value: NoNullableModel) {} + } + + const value = { + prop1: null, + prop2: null, + prop3: null, + prop5: null + }; + + const result = await validate(value, JsonParameterStore.get(Ctrl, "get", 0)); + + expect(result).toEqual({ + prop1: "", + prop2: 0, + prop3: "", + prop5: [""] + }); + }); + }); + describe("when returnsCoercedValues is true", () => { + beforeEach(() => + PlatformTest.create({ + ajv: { + verbose: true, + coerceTypes: "array", + returnsCoercedValues: true + } + }) + ); + afterEach(() => PlatformTest.reset()); + it("should validate object and return the coerced value (NullModel)", async () => { + class Ctrl { + get(@BodyParams() value: NullModel) {} + } + + const value = { + prop1: null, + prop2: null, + prop3: null, + prop4: null, + prop5: null + }; + + const result = await validate(value, JsonParameterStore.get(Ctrl, "get", 0)); + + expect(result).toEqual(value); + }); + it("should validate object and return the coerced value (NoNullableModel)", async () => { + class Ctrl { + get(@BodyParams() value: NoNullableModel) {} + } + + const value = { + prop1: null, + prop2: null, + prop3: null, + prop5: null + }; + + const result = await validate(value, JsonParameterStore.get(Ctrl, "get", 0)); + + expect(result).toEqual({ + prop1: "", + prop2: 0, + prop3: "", + prop5: [""] + }); + }); + }); +}); From 24b970e6d02e710dbc9f17cb5142fe225dc4e989 Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Thu, 2 Nov 2023 13:46:53 +0100 Subject: [PATCH 3/3] docs: describe the ajv.returnsCoercedValue --- docs/docs/model.md | 66 ++++++++++++++++++- docs/tutorials/ajv.md | 8 ++- packages/specs/ajv/jest.config.js | 2 +- .../integration/nullable.integration.spec.ts | 8 +-- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/docs/docs/model.md b/docs/docs/model.md index cc9dc5afc1a..8c504e0673d 100644 --- a/docs/docs/model.md +++ b/docs/docs/model.md @@ -129,6 +129,66 @@ The @@Nullable@@ decorator is used allow a null value on a field while preservin +::: warning + +Since the v7.43.0, `ajv.returnsCoercedValues` is available to solve the following issue: [#2355](https://github.com/tsedio/tsed/issues/2355) +If `returnsCoercedValues` is true, AjvService will return the coerced value instead of the original value. In this case, `@Nullable()` will be mandatory to +allow the coercion of the value to `null`. + +For example if `returnsCoercedValues` is `false` (default behavior), Ts.ED will allow null value on a field without `@Nullable()` decorator: + +```typescript +class NullableModel { + @Proprety() + propString: string; // null => null + + @Proprety() + propNumber: number; // null => null + + @Property() + propBool: boolean; // null => null +} +``` + +Ajv won't emit validation error if the value is null due to his coercion behavior. AjvService will return the original value and not the Ajv coerced value. +Another problem is, the typings of the model doesn't reflect the real coerced value. + +Using the `returnsCoercedValues` option, AjvService will return the coerced type. In this case, our previous model will have the following behavior: + +```typescript +class NullableModel { + @Proprety() + propString: string; // null => '' + + @Proprety() + propNumber: number; // null => 0 + + @Property() + propBool: boolean; // null => false +} +``` + +Now `@Nullable` usage is mandatory to allow `null` value on properties: + +```typescript +class NullableModel { + @Nullable(String) + propString: string | null; // null => null + + @Nullable(Number) + propNumber: number | null; // null => null + + @Nullable(Boolean) + propBool: boolean | null; // null => null +} +``` + +::: + +::: warning +`returnsCoercedValue` will become true by default in the next major version of Ts.ED. +::: + ## Any The @@Any@@ decorator is used to allow any types: @@ -677,7 +737,7 @@ So by using the @@deserialize@@ function with the extra groups options, we can m ```typescript -import { deserialize } from '@tsed/json-mapper'; +import {deserialize} from "@tsed/json-mapper"; const result = deserialize( { @@ -697,7 +757,7 @@ console.log(result); // User {firstName, lastName, email, password} ```typescript -import { deserialize } from '@tsed/json-mapper'; +import {deserialize} from "@tsed/json-mapper"; const result = deserialize( { @@ -718,7 +778,7 @@ console.log(result); // User {id, firstName, lastName, email, password} ```typescript -import { deserialize } from '@tsed/json-mapper'; +import {deserialize} from "@tsed/json-mapper"; const result = deserialize( { diff --git a/docs/tutorials/ajv.md b/docs/tutorials/ajv.md index 3ecb0f6e173..a3de6b68cbd 100644 --- a/docs/tutorials/ajv.md +++ b/docs/tutorials/ajv.md @@ -37,7 +37,9 @@ import {Configuration} from "@tsed/common"; import "@tsed/ajv"; // import ajv ts.ed module @Configuration({ - ajv: {} + ajv: { + returnsCoercedValues: true // returns coerced value to the next pipe instead of returns original value (See #2355) + } }) export class Server {} ``` @@ -227,6 +229,10 @@ describe("Product", () => { }); ``` +::: warning +If you planed to create keyword that transform the data, you have to set `returnsCoercedValues` to `true` in your configuration. +::: + ### With "code" function Starting from v7 Ajv uses [CodeGen module](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) for all pre-defined keywords - see [codegen.md](https://ajv.js.org/codegen.html) for details. diff --git a/packages/specs/ajv/jest.config.js b/packages/specs/ajv/jest.config.js index acdd0623247..689f803fc5c 100644 --- a/packages/specs/ajv/jest.config.js +++ b/packages/specs/ajv/jest.config.js @@ -9,7 +9,7 @@ module.exports = { }, coverageThreshold: { global: { - branches: 86.88, + branches: 87.87, functions: 100, lines: 99.71, statements: 99.71 diff --git a/packages/specs/ajv/test/integration/nullable.integration.spec.ts b/packages/specs/ajv/test/integration/nullable.integration.spec.ts index 2f316199cbf..db025e09a76 100644 --- a/packages/specs/ajv/test/integration/nullable.integration.spec.ts +++ b/packages/specs/ajv/test/integration/nullable.integration.spec.ts @@ -127,10 +127,10 @@ describe("Nullable model", () => { const result = await validate(value, JsonParameterStore.get(Ctrl, "get", 0)); expect(result).toEqual({ - prop1: "", - prop2: 0, - prop3: "", - prop5: [""] + prop1: null, + prop2: null, + prop3: null, + prop5: null }); }); });