Function keys

keys() returns an array of: a) Nestedly occurring keys or indexes (eg Set 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 only string or symbol types of props of your input value, based on your options (similar to _.keys() or Object.keys()). See discussion and more below.

Examples

  • returns a realObject's prop/keys (string by default, symbol optionally)

     z.keys({ a: 1, b: 2, c: 3 })           //=> ['a', 'b', 'c'] & type is ('a' | 'b' | 'c')[]
    
  • returns an Array's indexes (by default)

     z.keys(['a', 'b', 'c'])                //=> [0, 1, 2 ] & type is number[]
    
  • returns Set keys (by default)

     z.keys(new Set(['a', 'b', 'c']))       //=> ['a', 'b', 'c'] & type is ('a' | 'b' | 'c')[]
    
  • returns Map keys (by default)

     z.keys(new Map([['a', 1], ['b', 2], ['c', 3]])) //=> ['a', 'b', 'c'] & type is ('a' | 'b' | 'c')[]
    

If you use props: true, then all Object inputs are considered only for their props instead:

 const myArray = [1, 2, 3]

 myArray.stringProp = 'some Value'
 myArray[Symbol.for('symbolProp')] = 'some other Value'

 z.keys(myArray, { props: true }) // -> ['stringProp']

 // Optionally symbol keys as well
 z.keys(myArray, { props: true, symbol: true}) // -> [ 'stringProp', Symbol(symbolProp) ]

With props: true, you can also optionally include Object props such as:

  • string & symbol props. By default, only string props are included
  • own & inherited props. By default, only own props are included
  • enumerables & nonEnumerables props. By default, only enumerable props are included
  • top-level props like methods toString etc. By default, top level props are excluded
  • hidden top-level & hidden props, like __proto__, __defineGetter__ etc

See 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 input

Object.keys() & _.keys() return string[] props for all objects (including special objects like Map, 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:

Zen z.keys() rationale & stance

Typings 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 about Person'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 an Employee or Customer instance.

  • Our code should not try to get Employee.salary or Customer.loyaltyProgram, because it's not part of the Person interface. If our code deals only with Person.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 (eg Person) (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 (via z.keys() or z.loop()/z.each()), you can always switch only on the keys you know, and ignore the rest.

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 since Map (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.

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 as z.keys() (same implementation), but returns string[] keys (or (string | symbol)[] etc upon option's request), instead of the rigorously typed z.keys (like Object.keys()).

Parameters

  • Type Parameters

    Parameters

    • input: Tinput

      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).

    • Optionaloptions: Toptions

      KeysOptions to control the output of keys / props, including:

      • own: if true (default), include own props
      • string: if true (default), include string props
      • enumerables: 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 props
      • inherited: if true, include inherited props
      • nonEnumerables: if true, include non-enumerable props
      • top: if true (along with nonEnumerables), it includes top-level props
      • hidden: 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)

    Returns Keys<Tinput, Toptions>[]

    Array of keys or props of the input value, typed according to the input value & options.