Skip to content

Commit fb3c6fb

Browse files
committed
feat(P.object): Add P.object.exact
1 parent 42a461c commit fb3c6fb

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
@@ -37,6 +37,7 @@ import {
3737
ObjectChainable,
3838
ObjectPattern,
3939
EmptyObjectPattern,
40+
ObjectLiteralPattern,
4041
} from './types/Pattern';
4142

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

@@ -649,13 +651,17 @@ function isObject(x: unknown): x is object {
649651
return !!x && (typeof x === 'object' || typeof x === 'function');
650652
}
651653

652-
function isEmptyObject(x: unknown) {
654+
function hasExactKeys(keys: Set<PropertyKey>, x: unknown) {
653655
if (!x || typeof x !== 'object') return false;
654656
if (Array.isArray(x)) return false;
655-
for (const _key in x) return false;
657+
for (const key in x) if (!keys.has(key)) return false;
656658
return true;
657659
}
658660

661+
function isEmptyObject(x: unknown) {
662+
return hasExactKeys(new Set(), x);
663+
}
664+
659665
type AnyConstructor = abstract new (...args: any[]) => any;
660666

661667
function isInstanceOf<T extends AnyConstructor>(classConstructor: T) {
@@ -1109,6 +1115,31 @@ export const nullish: NullishPattern = chainable(when(isNullish));
11091115
*/
11101116
const emptyObject: EmptyObjectPattern = chainable(when(isEmptyObject));
11111117

1118+
/**
1119+
* `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.
1120+
*
1121+
* [Read the documentation for `P.object.exact()` on GitHub](https://github.com/gvergnaud/ts-pattern#pobjectexact)
1122+
*
1123+
* @example
1124+
* match(value)
1125+
* .with(
1126+
* P.object.exact({ a: P.any }),
1127+
* () => 'Objects with a single `a` key that contains anything.'
1128+
* )
1129+
*/
1130+
export function exactObject<
1131+
input,
1132+
const pattern extends ObjectLiteralPattern<input>
1133+
>(
1134+
pattern: pattern
1135+
): Chainable<GuardExcludeP<input, InvertPattern<pattern, input>, never>>;
1136+
export function exactObject(pattern: ObjectLiteralPattern<{}>) {
1137+
const patternKeys = new Set(Object.keys(pattern));
1138+
return chainable(
1139+
when((input) => isMatching(pattern) && hasExactKeys(patternKeys, input))
1140+
);
1141+
}
1142+
11121143
/**
11131144
* `P.object` is a wildcard pattern, matching any **object**.
11141145
*

src/types/Pattern.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { None, Some, SelectionType } from './FindSelected';
99
import { matcher, narrow } from '../patterns';
1010
import { ExtractPreciseValue } from './ExtractPreciseValue';
11+
import { InvertPattern } from './InvertPattern';
1112

1213
export type MatcherType =
1314
| 'not'
@@ -169,7 +170,7 @@ type KnownPatternInternal<
169170
: ObjectLiteralPattern<Readonly<MergeUnion<objs>>>)
170171
| ([arrays] extends [never] ? never : ArrayPattern<arrays>);
171172

172-
type ObjectLiteralPattern<a> =
173+
export type ObjectLiteralPattern<a> =
173174
| {
174175
readonly [k in keyof a]?: Pattern<a[k]>;
175176
}
@@ -684,6 +685,22 @@ export type ObjectChainable<
684685
* .with(P.object.empty(), () => 'will match on empty objects')
685686
*/
686687
empty: () => EmptyObjectPattern;
688+
689+
/**
690+
* `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.
691+
*
692+
* [Read the documentation for `P.object.exact()` on GitHub](https://github.com/gvergnaud/ts-pattern#pobjectexact)
693+
*
694+
* @example
695+
* match(value)
696+
* .with(
697+
* P.object.exact({ a: P.any }),
698+
* () => 'Objects with a single `a` key that contains anything.'
699+
* )
700+
*/
701+
<input, const pattern extends ObjectLiteralPattern<input>>(
702+
pattern: pattern
703+
): Chainable<GuardExcludeP<input, InvertPattern<pattern, input>, never>>;
687704
},
688705
omitted
689706
>;

0 commit comments

Comments
 (0)