From e49b9ffb4dc035809ddfc02fb05945bb41f611c7 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 30 Jun 2026 14:16:58 +0200 Subject: [PATCH] feat: add exactOptional support in type struct --- src/structs/types.ts | 6 ++++++ test/typings/exactOptional.ts | 15 +++++++++++++ .../type/invalid-exact-optional-undefined.ts | 21 +++++++++++++++++++ .../type/valid-exact-optional-absent.ts | 12 +++++++++++ .../type/valid-exact-optional-present.ts | 13 ++++++++++++ 5 files changed, 67 insertions(+) create mode 100644 test/validation/type/invalid-exact-optional-undefined.ts create mode 100644 test/validation/type/valid-exact-optional-absent.ts create mode 100644 test/validation/type/valid-exact-optional-present.ts diff --git a/src/structs/types.ts b/src/structs/types.ts index b40a1bd4..26c9e01e 100644 --- a/src/structs/types.ts +++ b/src/structs/types.ts @@ -684,6 +684,12 @@ export function type( *entries(value) { if (isObject(value)) { for (const k of keys) { + if ( + ExactOptionalStruct.isExactOptional(schema[k]) && + !Object.prototype.hasOwnProperty.call(value, k) + ) { + continue; + } yield [k, value[k], schema[k] as Struct]; } } diff --git a/test/typings/exactOptional.ts b/test/typings/exactOptional.ts index 2daf616b..66941818 100644 --- a/test/typings/exactOptional.ts +++ b/test/typings/exactOptional.ts @@ -4,6 +4,7 @@ import { string, number, object, + type, enums, never, record, @@ -60,3 +61,17 @@ test<{ ); return value; }); + +test<{ + a?: number; + b: string; +}>((value) => { + assert( + value, + type({ + a: exactOptional(number()), + b: string(), + }), + ); + return value; +}); diff --git a/test/validation/type/invalid-exact-optional-undefined.ts b/test/validation/type/invalid-exact-optional-undefined.ts new file mode 100644 index 00000000..dc2cbb99 --- /dev/null +++ b/test/validation/type/invalid-exact-optional-undefined.ts @@ -0,0 +1,21 @@ +import { type, exactOptional, string, number } from '../../../src'; + +export const Struct = type({ + name: string(), + age: exactOptional(number()), +}); + +export const data = { + name: 'john', + age: undefined, +}; + +export const failures = [ + { + value: undefined, + type: 'number', + refinement: undefined, + path: ['age'], + branch: [data, data.age], + }, +]; diff --git a/test/validation/type/valid-exact-optional-absent.ts b/test/validation/type/valid-exact-optional-absent.ts new file mode 100644 index 00000000..2e4f6e88 --- /dev/null +++ b/test/validation/type/valid-exact-optional-absent.ts @@ -0,0 +1,12 @@ +import { type, exactOptional, string, number } from '../../../src'; + +export const Struct = type({ + name: string(), + age: exactOptional(number()), +}); + +export const data = { + name: 'john', +}; + +export const output = data; diff --git a/test/validation/type/valid-exact-optional-present.ts b/test/validation/type/valid-exact-optional-present.ts new file mode 100644 index 00000000..34407b46 --- /dev/null +++ b/test/validation/type/valid-exact-optional-present.ts @@ -0,0 +1,13 @@ +import { type, exactOptional, string, number } from '../../../src'; + +export const Struct = type({ + name: string(), + age: exactOptional(number()), +}); + +export const data = { + name: 'john', + age: 42, +}; + +export const output = data;