diff --git a/README.md b/README.md index e6b2b847..94f52d3f 100644 --- a/README.md +++ b/README.md @@ -1545,6 +1545,23 @@ console.log(isMatching(P.object.empty(), null)); // false console.log(isMatching(P.object.empty(), undefined)); // false ``` +### `P.object.exact({...})` + +`P.object.exact({...})` matches objects that contain exactly the set of defined in the pattern. + +```ts +import { match, P } from 'ts-pattern'; + +const fn = (input: unknown) => + match(input) + .with(P.object.exact({ a: P.any }), () => 'Objects with a single `a` key that contains anything.') + .otherwise(() => '❌'); + +console.log(fn({})); // ❌ +console.log(fn({ a: 1 })); // ✅ +console.log(fn({ a: 1, b: 2 })); // ❌ +``` + ## Types ### `P.infer` diff --git a/src/types/Pattern.ts b/src/types/Pattern.ts index 6ca297c1..d6e295bd 100644 --- a/src/types/Pattern.ts +++ b/src/types/Pattern.ts @@ -695,7 +695,7 @@ export type ObjectChainable< * () => 'Objects with a single `a` key that contains anything.' * ) */ - >( + exact>( pattern: pattern ): Chainable, never>>; }, diff --git a/tests/object.test.ts b/tests/object.test.ts index 3c73efa7..38bdfbed 100644 --- a/tests/object.test.ts +++ b/tests/object.test.ts @@ -106,4 +106,22 @@ describe('Object', () => { expect(fn(null)).toEqual('no'); }); }); + + describe('P.object.exact({...})', () => { + it('should only catch the literal `{}`.', () => { + const fn = (input: object) => + match(input) + .with(P.object.exact({ a: P.any }), (obj) => { + type t = Expect>; + return 'yes'; + }) + // @ts-expect-error: non empty object aren't caught + .exhaustive(); + expect(fn({})).toEqual('yes'); + expect(() => fn({ hello: 'world' })).toThrow(); + expect(() => fn(() => {})).toThrow(); + expect(() => fn([1, 2, 3])).toThrow(); + expect(() => fn([])).toThrow(); + }); + }); });