Skip to content

Instantly share code, notes, and snippets.

@JakeSidSmith
Last active August 7, 2023 18:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JakeSidSmith/a5ddd931c2b00472ab232b95077b91cb to your computer and use it in GitHub Desktop.
Save JakeSidSmith/a5ddd931c2b00472ab232b95077b91cb to your computer and use it in GitHub Desktop.
Strict types in TypeScript with getters and setters
/* Tested with TypeScript 2.4.2 */
// First we define an interface for our object
interface IThing {
id: string;
type: 'foo' | 'bar';
num?: number;
}
// Then we can use this Exact type to prevent type inference on our objects
type Exact<T> = {[K in keyof T]: T[K]};
// We call Exact with our objects interface
// This ensures that our object is always created with the exact keys & values required by the interface
let thing: Exact<IThing> = {
id: 'test',
type: 'foo',
num: 1,
};
// Now when we access the keys, all of the types of these values are the exact types defined in our interface
let id = thing.id // string
let type = thing.type // 'foo' | 'bar'
let num = thing.num // number | undefined
// Here we're defining an API with getters and setters for our object
// The objects type (I) is inferred, and if we're passing an Exact object, it'll know all the interface types
function MagicMap<I> (obj: I) {
// In order to return the correct types we extend the keys of this object
// Don't ask me why, but if you don't let K extend, you get less exact types
function get<K extends keyof I> (k: K) {
return obj[k];
}
// Same for the setter
// But here we are also passing a value that uses K to get the exact values that can be passed in to the setter also
function set<K extends keyof I> (k: K, value: typeof obj[K]) {
obj[k] = value;
return obj;
}
return {
get,
set,
};
}
const magicMap = MagicMap(thing);
// Now all of the types returned by the getters are the exact types you'd expect from the interface
id = magicMap.get('id'); // string
type = magicMap.get('type'); // 'foo' | 'bar'
num = magicMap.get('num'); // number | undefined
// And it wont let you set a less exact value on any key
// E.g. thing's type is 'foo' | 'bar', so simply passing another string is not allowed
thing = magicMap.set('type', 'string');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment