Search code examples
javascriptjsdocjsdoc3

How to write a JSDoc for an object with different types of keys?


The JSDoc api says you can document objects like so:

{Object.<string, number>}

and document multiple type:

{(number|boolean)}

But if I try to specify an object that could have strings OR numbers as the key, it does not work. VSCode/JSDoc just reports the type as 'any'.

VSCode does not understand:

/**
 * Object with string or number for keys
 * @param {Object.<(string|number), any>} Container
 */

I've also tried this in @typedef, or defining the key in it's own @typedef to no effect.

Because I'm using & to get an intersection of types (like {Object.<string, any> & {'foo': number}} I don't want to have to use the boolean or to say:

/**
 * Object with string or number for keys
 * @param {(Object.<string, any>|Object.<number, any>) & {'foo': number}} Container
 */

The type documented ends up looking something like:

 type Container = ({
    [x: string]: any;
  } & {
    'foo': number;
  }) | ({
    [x: number]: any;
  } & {
    'foo': number;
  })

Which is needlessly verbose.

Is there way to document this with a more succinct output?


Solution

  • In JavaScript, object keys are always strings (or, in the case of numbers, coerced into strings), so you might be needlessly complicating things. See the ECMAScript spec on Objects:

    Properties are identified using key values. A property key value is either an ECMAScript String value or a Symbol value. All String and Symbol values, including the empty String, are valid as property keys. A property name is a property key that is a String value.

    An integer index is a String-valued property key that is a canonical numeric String

    That said, this seems like the most straightforward solution:

    // Combined
    /**
     * @param {Object.<string, any> & {foo:  number}} Container
     */
    
    // Split/reusable
    /**
     * @typedef {Object.<string, any>} GenericObject
     * @param {GenericObject & {foo: number}} Container
     */
    

    Both of the above result in this type/documentation:

    Container: {
        [x: string]: any;
    } & {
        foo: number;
    }
    

    Declaring Object.<string, any> seems a bit redundant to me since object keys are inherently strings and values are inherently any, so declaring it this way doesn't provide much value to a developer.