any input value that is an Object (eg Array, Map, Set, Function, Iterator, Generator, etc). Primitive
values are allowed by default, but return an empty array of never[]
(with strict: false
, the default).
Optional
options: ToptionsKeysOptions to control the output of keys / props, including:
own
: if true
(default), include own propsstring
: if true
(default), include string propsenumerables
: if true
(default), include enumerable props
And these are false
/undefined
by default:props
: if true
, only props are returned, instead Nested Keys. And 'all'
will bring all!symbol
: if true
, include symbol propsinherited
: if true
, include inherited propsnonEnumerables
: if true
, include non-enumerable propstop
: if true
(along with nonEnumerables
), it includes top-level propshidden
: if true
(along with nonEnumerables
& top
), it includes top-level hidden props.strict
: if true
, only _.isObject
values are allowed, throws otherwise (i.e on Primitives
)Array of keys or props of the input value, typed according to the input value & options.
keys()
returns an array of: a) Nestedly occurringkeys
orindexes
(egSet
keys, array indexes etc) or b)props
of the input value (props on all objects), including symbols, own & inherited, enumerable & nonEnumerable! The returned props can be filtered by prop type (string or symbol), enumerability, own/inherited, top-level props etc.Unlike
Object.keys()
/_.keys()
, it returns the Nested Keys of the "special objects"Array
,Map
,Set
&TypedArray
(by default).Typings choice
The
keys()
&keysS()
variants share the same implementation and same runtime results, but differ in the props types they return in TypeScript:keys()
is typed rigorously to return you the exact types of props of your input value, based on your options.keysS()
returns onlystring
orsymbol
types of props of your input value, based on your options (similar to_.keys()
orObject.keys()
). See discussion and more below.Examples
returns a
realObject
's prop/keys (string
by default,symbol
optionally)returns an
Array
's indexes (by default)returns
Set
keys (by default)returns
Map
keys (by default)If you use
props: true
, then all Object inputs are considered only for their props instead:With
props: true
, you can also optionally include Object props such as:string
&symbol
props. By default, onlystring
props are includedown
&inherited
props. By default, onlyown
props are includedenumerables
&nonEnumerables
props. By default, only enumerable props are includedtop
-level props like methodstoString
etc. By default, top level props are excludedhidden
top-level & hidden props, like__proto__
,__defineGetter__
etcSee more at
KeysOptions
Adapted from
getAllKeysConditionally
at https://stackoverflow.com/questions/8024149/is-it-possible-to-get-the-non-enumerable-inherited-property-names-of-an-object/70629468#70629468 inputObject.keys()
&_.keys()
returnstring[]
props for all objects (including special objects likeMap
,Set
,Array
,TypedArray
etc, whose props are usually not interesting). This is how JS works, and TypeScript always follows.But in the typings realm, TypeScript is simply returns
string[]
for all objects, instead of returning the actual key literals that it knows about. This is how TypeScript is made, and the TS team is keen to not changing it (see this issue of TS's father since 2016).More Background:
keyof typeof
&Object.keys()
Iteration ExampleInvariantOf
Zen
z.keys()
rationale & stanceTypings Rationale
We return rigorously typed keys in
keys()
, based on the input value while respecting all the options passed and the eventual visibility, because it is useful in many cases and people are relying on extra boilerplate to achieve it.The TS team's retionale for not including it, is that you will end up receiving more (or less) keys than you expect, and that's a valid point. But we think that's a good thing, because it's a feature of JS, and we should be able to handle it gracefully, without throwing errors.
The main mantra is: when we use
keys()
, we should NOT care/throw about extra keys you might receive, we just ignore them!Assume we have a
function sendLetterToPerson(person: Person) {}
, we should only care aboutPerson
's keys,Person
's functionality.Our function should not care about other keys that may be present in the object, eg if the
person
is actually anEmployee
orCustomer
instance.Our code should not try to get
Employee.salary
orCustomer.loyaltyProgram
, because it's not part of thePerson
interface. If our code deals only withPerson.name
&Person.address
(which are enough for the purpose of sending a letter), and doesn't throw if extra props exists, everything will be fine!We could enforce exact types or invariants (eg via type-fest's InvariantOf), but it still misses the purpose (and it's kinda ugly). Because the point is that we should be able to pass sub-instances to our functions, and that's the OO & JS way and it's fine, but without sacrificing keys & values typings. If those functions act only on what they should know, we should be fine!
So, the rule is: just don't bother with extra keys you might receive. Either ignore them OR handle them gracefully, without throwing! Especially if you're dealing with an
z.isInstance()
object (egPerson
) (or a similar interface), you should NOT be able to access any other member, and that's how it works with all mainstream OO languages! When you're iterating over it for any reason (viaz.keys()
orz.loop()
/z.each()
), you can alwaysswitch
only on the keys you know, and ignore the rest.Historical Reasons
Part of the problem arises from JavaScript classes, who's instances are just plain objects, with an added constructor and a hidden
__proto__
for the inheritance chain.But Plain Objects in JS are also very commonly being used as property bags (i.e. the infamous
{prop: 'value'}
object), and sinceMap
(that is the right data structure for this job) has arrived late to the party and with breaking functionality and support (lodash still doesn't_.each
on Map/Set), most code still uses plain objects most of the time! But iterating over key/values & object classes & inheritance are fundamentally different things, but we use the same data structure for both, and that's one reason the problem is manifested.Typings are optional!
If you still insist on being pure on TS's dictation, then you can use the plain
z.keysS()
function, which is exactly the same asz.keys()
(same implementation), but returnsstring[]
keys (or(string | symbol)[]
etc upon option's request), instead of the rigorously typedz.keys
(likeObject.keys()
).Parameters