Home > database >  JavaScript: How to get a list of object keys of function argument object
JavaScript: How to get a list of object keys of function argument object

Time:10-16

I am using an object to pass in arguments, because the function has many parameters, and JavaScript doesnt support named arguments like Python.

I would like to check that all the args have a value passed in. To do that I would like to loop over an array of the parameters, ['foo', 'bar', 'baz'] in the example below.

I would like to keep the code dry and not hardcode the list. I can access an object of the arguments with arguments[0], but how does one access the parameters ['foo', 'bar', 'baz'] programatically without hardcoding it?

function run({foo, bar, baz}) {
    const requiredKeys = ['foo', 'bar', 'baz']; // <- How to dynamically generate this
    for (const key in requiredKeys) {
        if (arguments[key] === null) throw `Please provide a value for ${key}`
    }
    console.log('done:', foo, bar, baz);
}
run({ foo: 1});

CodePudding user response:

Don't destructure the argument (at least not initially) - instead, declare an array of the required keys into the code, then iterate over the array.

function run(obj) {
    const requiredKeys = ['foo', 'bar', 'baz'];
    for (const key of requiredKeys) {
        if (obj[key] === null || obj[key] === undefined) {
            throw new Error(`Please provide a value for ${key}`);
        }
    }
    console.log('done');
}
run({ foo: 1});

A nicer way would be to use JSDoc or TypeScript to indicate to the consumer of run that it must be called with a particular type of object - that way mistakes can be caught while writing code, rather than only when it's running.

CodePudding user response:

const findMissingKeys = (obj, keys) => {
  const missingKeys = []
  keys.forEach(key => {
    if(!obj.hasOwnProperty(key) || obj[key] === undefined || obj[key] === null) {
      missingKeys.push(key)
    }
  });  
  return missingKeys;
}

You can do something similar to this.

Edit 1: Just saw that you need values to be passed as well. Changing the code accordingly

CodePudding user response:

Not everyone will see this as good practice, but you could use the source code of the function to extract the names of the properties of the parameter. Mozilla Contributors note:

Since ES2018, the spec requires the return value of toString() to be the exact same source code as it was declared, including any whitespace and/or comments

This makes parsing that string as reliable as the parsing system used.

For instance, you could use a decorator function that will extract the destructured parameter of the given function and return a function that will make the argument check:

// Decorator
function requireArgsFor(f) {
    // Extract (once) the destructered parameter definition. 
    // This assumes it is present as first parameter, and is not nested:
    const params = /\(\s*\{([^}]*)/.exec(f)[1].split(",").map(param => param.trim());
    return { // Object wrapper to ensure the decorated function gets the same name
        [f.name](obj) {
            const missing = params.find(param => !Object.hasOwn(obj, param));
            if (missing) throw TypeError(`Argument ${missing} is missing in call of ${f.name}`);
            return f(obj);
        }
    }[f.name]; 
}

function run({foo, bar, baz}) {
    console.log('run is running');
}

// Decorate the above function
run = requireArgsFor(run);
console.log(`calling ${run.name} with all required arguments...`);
run({ foo: 1, bar: 2, baz: 3 });
console.log(`calling ${run.name} with missing arguments...`);
run({ foo: 1 }); // Will trigger TypeError

You'd have to improve on the parsing part if you want to support more variations:

  • For supporting default values, like `c
  • For allowing "unnamed" parameters to precede this final parameter, like (a, b, {foo, bar, baz})
  • For supporting nested properties or arrays, like ({ foo: { bar }, baz: [first, ...rest] })
  • ...etc
  • Related