Skip to content

Commit 6cc4989

Browse files
committed
feat(P.object): Add P.object.exact
1 parent e6bf0af commit 6cc4989

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

src/patterns.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
ObjectChainable,
3939
ObjectPattern,
4040
EmptyObjectPattern,
41+
ObjectLiteralPattern,
4142
} from './types/Pattern';
4243

4344
export type { Pattern, Fn as unstable_Fn };
@@ -164,6 +165,7 @@ function objectChainable<pattern extends Matcher<any, any, any, any, any>>(
164165
): ObjectChainable<pattern> {
165166
return Object.assign(chainable(pattern), {
166167
empty: () => emptyObject,
168+
exact: exactObject,
167169
}) as any;
168170
}
169171

@@ -654,13 +656,17 @@ function isObject(x: unknown): x is object {
654656
return !!x && (typeof x === 'object' || typeof x === 'function');
655657
}
656658

657-
function isEmptyObject(x: unknown) {
659+
function hasExactKeys(keys: Set<PropertyKey>, x: unknown) {
658660
if (!x || typeof x !== 'object') return false;
659661
if (Array.isArray(x)) return false;
660-
for (const _key in x) return false;
662+
for (const key in x) if (!keys.has(key)) return false;
661663
return true;
662664
}
663665

666+
function isEmptyObject(x: unknown) {
667+
return hasExactKeys(new Set(), x);
668+
}
669+
664670
type AnyConstructor = abstract new (...args: any[]) => any;
665671

666672
function isInstanceOf<T extends AnyConstructor>(classConstructor: T) {
@@ -1124,6 +1130,31 @@ export const nonNullable: NonNullablePattern = chainable(when(isNonNullable));
11241130
*/
11251131
const emptyObject: EmptyObjectPattern = chainable(when(isEmptyObject));
11261132

1133+
/**
1134+
* `P.object.exact({...})` matching objects that contain exactly the set of defined in the pattern. Objects with additional keys won't match this pattern, even if keys defined in both the pattern and the object match.
1135+
*
1136+
* [Read the documentation for `P.object.exact()` on GitHub](https://github.com/gvergnaud/ts-pattern#pobjectexact)
1137+
*
1138+
* @example
1139+
* match(value)
1140+
* .with(
1141+
* P.object.exact({ a: P.any }),
1142+
* () => 'Objects with a single `a` key that contains anything.'
1143+
* )
1144+
*/
1145+
export function exactObject<
1146+
input,
1147+
const pattern extends ObjectLiteralPattern<input>
1148+
>(
1149+
pattern: pattern
1150+
): Chainable<GuardExcludeP<input, InvertPattern<pattern, input>, never>>;
1151+
export function exactObject(pattern: ObjectLiteralPattern<{}>) {
1152+
const patternKeys = new Set(Object.keys(pattern));
1153+
return chainable(
1154+
when((input) => isMatching(pattern) && hasExactKeys(patternKeys, input))
1155+
);
1156+
}
1157+
11271158
/**
11281159
* `P.object` is a wildcard pattern, matching any **object**.
11291160
*

src/types/Pattern.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { MergeUnion, Primitives, WithDefault } from './helpers';
33
import { None, Some, SelectionType } from './FindSelected';
44
import { matcher } from '../patterns';
55
import { ExtractPreciseValue } from './ExtractPreciseValue';
6+
import { InvertPattern } from './InvertPattern';
67

78
export type MatcherType =
89
| 'not'
@@ -164,7 +165,7 @@ type KnownPatternInternal<
164165
: ObjectLiteralPattern<Readonly<MergeUnion<objs>>>)
165166
| ([arrays] extends [never] ? never : ArrayPattern<arrays>);
166167

167-
type ObjectLiteralPattern<a> =
168+
export type ObjectLiteralPattern<a> =
168169
| {
169170
readonly [k in keyof a]?: Pattern<a[k]>;
170171
}
@@ -681,6 +682,22 @@ export type ObjectChainable<
681682
* .with(P.object.empty(), () => 'will match on empty objects')
682683
*/
683684
empty: () => EmptyObjectPattern;
685+
686+
/**
687+
* `P.object.exact({...})` matching objects that contain exactly the set of defined in the pattern. Objects with additional keys won't match this pattern, even if keys defined in both the pattern and the object match.
688+
*
689+
* [Read the documentation for `P.object.exact()` on GitHub](https://github.com/gvergnaud/ts-pattern#pobjectexact)
690+
*
691+
* @example
692+
* match(value)
693+
* .with(
694+
* P.object.exact({ a: P.any }),
695+
* () => 'Objects with a single `a` key that contains anything.'
696+
* )
697+
*/
698+
<input, const pattern extends ObjectLiteralPattern<input>>(
699+
pattern: pattern
700+
): Chainable<GuardExcludeP<input, InvertPattern<pattern, input>, never>>;
684701
},
685702
omitted
686703
>;

0 commit comments

Comments
 (0)