Skip to content

Instantly share code, notes, and snippets.

@itsjohncs
Created October 6, 2021 19:29
Show Gist options
  • Save itsjohncs/d1dce0f5b31849ca71dbcd3be803b747 to your computer and use it in GitHub Desktop.
Save itsjohncs/d1dce0f5b31849ca71dbcd3be803b747 to your computer and use it in GitHub Desktop.
An attempt at simulating an Exact data type in TS using existing tools.
// If all of Candidate's properties also exist in Model, this will be
// equivalent to Model. However, if Candidate has extra properties, they will
// be retyped to never and then merged into the resulting type.
//
// >>> ExclusivelyModel<{}, {a: number}>
// ... {a: number}
// >>> ExclusivelyModel<{a: number, b: number}, {a: number}>
// ... {a: number, b: never}
// >>> ExclusivelyModel<{a: string, b: number}, {a: number}>
// ... {a: number, b: never}
//
type ExclusivelyModel<Candidate, Model> = {
[Property in keyof Required<Candidate> | keyof Model]:
Property extends keyof Model ? Model[Property] : never;
}
// Recursive version, barely tested, use at your peril
type ExclusivelyModelDeep<Candidate, Model> = {
[Property in keyof Required<Candidate> | keyof Model]:
Property extends keyof Model ? (
Property extends keyof Required<Candidate> ? (
Model[Property] extends object ?
ExclusivelyModelDeep<Required<Candidate>[Property], Model[Property]>
: Model[Property])
: Model[Property])
: never
}
interface Foo {
a: number;
}
function mustBeExactlyFoo<T>(foo: ExclusivelyModel<T, Foo>): Foo {
return foo;
}
// This works how you'd want
const notExactlyFoo = {a: 1, b: 2};
// @ts-expect-error
console.log(mustBeExactlyFoo(notExactlyFoo));
// Ultimately its very easy to fool though. You just need to lose some
// information about the type. And since "exact" semantics are only in
// play when your explicitly using ExclusivelyModel... You can just do:
const alsoNotExactlyFoo: Foo = notExactlyFoo;
console.log(mustBeExactlyFoo(alsoNotExactlyFoo)); // :(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment