Last active
February 2, 2023 19:51
-
-
Save ryankshaw/0766f1c2832f34bbac30e04ba8d13620 to your computer and use it in GitHub Desktop.
a couple typescript type-aware helpers for iterating objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type PickByValue<T, V> = Pick< | |
T, | |
{ [K in keyof T]: T[K] extends V ? K : never }[keyof T] | |
> | |
type Entries<T> = { | |
[K in keyof T]: [keyof PickByValue<T, T[K]>, T[K]] | |
}[keyof T][] | |
/** | |
* A better Object.entries _BUT ONLY FOR OBJECTS YOU KNOW DON'T HAVE EXTRA PROPERTIES_, | |
* | |
* Explanation of why Typescript doesn't give a stronger type to Object.entries: | |
* | |
* When you have a type like type Obj = {a: number, b: string, c: number}, it's only | |
* guaranteed that a value has those properties; it is not guaranteed that the value does not | |
* also have other properties. For example, the value {a: 1, b: 'foo', c: 2, d: false} | |
* is assignable to the type Obj (excess property checking for object literals aside). | |
* | |
* In this case Object.entries would return an array containing the element ['d', false]. | |
* The type Entries<Obj> says this cannot happen, but in fact it can happen; so Entries<T> | |
* is not a sound return type for Object.entries in general. You should only use this | |
* function when you yourself know that the values will have no excess properties. | |
* | |
* from: https://stackoverflow.com/questions/60141960/typescript-key-value-relation-preserving-object-entries-type | |
*/ | |
export const entries = <T extends object>(obj: T) => | |
Object.entries(obj) as Entries<T> | |
/** | |
* The return type of Object.keys({foo: 1, bar: 2}) is `string[]`. | |
* If you use this, the return type will be `("foo" | "bar")[]`. | |
* from: https://stackoverflow.com/a/59459000 | |
*/ | |
export const keys = Object.keys as <T extends object>(obj: T) => Array<keyof T> | |
// USAGE: | |
const foo = { | |
bar: Math.random() > 0.5 ? 'a' : 'b', | |
bang: 1, | |
} as const | |
Object.entries(foo).forEach(([key, value]) => { | |
// key is `string` (not the more narrowed `'bar' | 'bang'`) | |
// value is `'a' | 'b' | 1` | |
if (key === 'bar') { | |
// no narrowing of value | |
} else if (key === 'bang') { | |
// no narrowing of value | |
} else { | |
// typescript doesn't know we can't get here. key is `string` | |
} | |
}) | |
entries(foo).forEach(([key, value]) => { | |
// key is `'bar' | 'bang'` | |
if (key === 'bar') { | |
// value is `'a' | 'b'` (the narrowest) | |
} else if (key === 'bang') { | |
// value is `1` (the narrowest) | |
} else { | |
// typescript knows we can't get here. key is `never` | |
} | |
}) | |
keys(foo).forEach((key) => { | |
if (key === 'bar') { | |
const value = foo[key] | |
// value is `'a' | 'b'` (the narrowest) | |
} else if (key === 'bang') { | |
const value = foo[key] | |
// value is `1` (the narrowest) | |
} else { | |
// typescript knows we can't get here. key is `never` | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment