1

Suppose I have a function (possibly from a 3rd party library, assume I can't change its definition), and an object with properties that match or overlap the function's arguments:

function fn(foo, bar, baz) { /* do stuff */ }
var obj = { foo: "yes", bar: 7, baz: false }

Is there a way to apply the objects properties as the functions arguments using some kind of destructuring or spread assignment, or some other ES6 feature, or am I stuck with specifying each argument seperately?

fn(...obj); // Doesn't work
fn(obj.foo, obj.bar, obj.baz); // Convoluted but works
fn.apply(null, obj.values()); // Might work if you're lucky
wensveen
  • 783
  • 10
  • 20
  • 4
    *"or am I stuck with specifying each argument seperately"* Pretty much. There is [a hacky solution](https://stackoverflow.com/a/11796776/218196) involving inspecting the function's string representation, but ... well, it's hacky. – Felix Kling Sep 10 '20 at 12:49
  • `fn.apply(null, Object.values(obj))` works fine. Remember it's the order of the arguments that matter, not the names. – evolutionxbox Sep 10 '20 at 12:49
  • @evolutionxbox depending on the object's property order seems like an extremely dangerous thing to do. – wensveen Sep 10 '20 at 12:56
  • @FelixKling That is a cool solution, but very hacky indeed :) – wensveen Sep 10 '20 at 12:57
  • 1
    You gotta ask yourself if you're going to use this enough to justify having it. The hack to find the function parameter names is cryptic to anyone who isn't familiar with it. So do you really want something like that sitting in the codebase? How many lines of code overall is it going to save you? It's probably better to just pass the arguments manually because it's going to end up being more clear to readers what is going on. – Chris Rollins Sep 10 '20 at 13:10

3 Answers3

4

You could create a wrapper function for fn:

const myFn = ({foo, bar, baz}) => fn(foo, bar, baz);

And then your call simplifies to:

myFn(obj);
trincot
  • 317,000
  • 35
  • 244
  • 286
1

This is an ugly hack and should probably be avoided but it does work (even if the object's values aren't in the same order as the parameters).

const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
  const fnStr = func.toString().replace(STRIP_COMMENTS, '');
  let result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
  if(result === null)
     result = [];
  return result;
}

function fn(foo, bar, baz) {
  return {foo, bar, baz};
}

const obj = { foo: "yes", baz: false, bar: 7 };

const result = fn(...getParamNames(fn).map(name => obj[name]));
console.log(result);

Note: Credit for the getParamNames function goes to How to get function parameter names/values dynamically?

Rocky Sims
  • 3,523
  • 1
  • 14
  • 19
0

You should destructure the key-object pairs inside the function.

const fn = (opts) => {
  const { foo, bar, baz } = opts; // Your function's "parameters"
  console.log({ foo, bar, baz });
}

const obj = { PI: Math.PI, bar: 7, baz: false, foo: "yes" };

fn(obj);

The naive approach would be to call the function using the spread operator (or apply-null technique), but this does not guarantee that the key-value pairs within the object align with the expected order of the parameters.

function fn(foo, bar, baz) {
  console.log(arguments);
}

const obj = { foo: "yes", bar: 7, baz: false };

fn(...Object.values(obj)); // or fn.apply(null, Object.values(obj))
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • This only works when the order of the object properties and the function arguments match, which is often not the case. – wensveen Sep 10 '20 at 12:54
  • @wensveen What are you wanting then? – evolutionxbox Sep 10 '20 at 12:55
  • This would be the correct answer if I could change the function's definition. I decided to go with the wrapper, which combines you answer with the original function definition. – wensveen Sep 10 '20 at 13:11