8

I'm looking for a vanilla JavaScript solution.

Say I've got a function with the following header:

generateEmail(firstName, lastname, provider)

I need to run it like this:

generateEmail("John","Smith","gmail.com");

I would like to be able to call it with argument map instead of positional arguments, i.e.

generateEmail({
  "firstName":"John",
  "lastname": "Smith",
  "provider": "gmail.com"
});

And I'm looking for an already-written solution to do this in JavaScript, since I've got an unlimited number of functions such as generateEmail above to handle. Does such library exist?

I have seen https://github.com/kilianc/node-introspect which handles function introspection (returning function abstract parameter information). But the second part is missing - mapping map-call into positional-call.

Please tell me, whether such thing exists.


edit: if I didn't make myself clear: I don't want to modify the original positional-argument function. I get such functions from an external provider which may update his code. I'd rather prefer to have a wrapper that could call the original function beneath, and provide a map-argument API outside.

ducin
  • 25,621
  • 41
  • 157
  • 256
  • You could check if `firstName` is an object and then call `generateEmail` again with proper arguments. – Gaurang Tandon May 04 '15 at 12:28
  • 1
    @GaurangTandon this is completely what I **don't** need. I need a generic wrapper function that handles what I've written. I can write it myself, but first I want to check, if such thing exists already. – ducin May 04 '15 at 12:32
  • I'd say using a combination of this http://stackoverflow.com/questions/914968/inspect-the-names-values-of-arguments-in-the-definition-execution-of-a-javascrip and `.apply` this would be possible. – Evan Knowles May 04 '15 at 12:43
  • @EvanKnowles I knew the link and I knew to use `.apply`; mainly I knew how to write such thing, but I don't like to reinvent the wheel :) I prefer to use existing solutions. – ducin May 04 '15 at 12:59

3 Answers3

3

Assuming you have access to introspect from node-introspect (which takes a function and returns an ordered list of arguments names), you can simply do:

function objArgsify(fn) {
    var argNames = introspect(fn);
    return function(obj) {
        return fn.apply(this,
                        argNames.map(function(a) { return obj[a]; });
    }
}

Call it by:

var generateEmailWithObjArgs = objArgsify(generateEmail);
generateEmailWithObjArgs({
  "firstName":"John",
  "lastname": "Smith",
  "provider": "gmail.com"
});

This accepts a function, reads its argument names, and then returns a wrapper function that accepts an object and uses the positional argument names to pull properties from the object-argument in the correct order.

This function uses the call-time object-argument as a map to transform the array ["firstName", "lastname", "provider"] into the array ["John", "Smith", "gmail.com"]. That array is then used with apply to invoke the postional-argument function.

apsillers
  • 112,806
  • 17
  • 235
  • 239
  • Much neater than my solution. – Evan Knowles May 04 '15 at 12:53
  • I was hoping that there is a library that handles it, I just didn't want to reinvent the wheel. Anyway, your solution aims at my needs; I was thinking more about: `var argMapify = function(fn, argValues, context) { var argDefs = introspect(fn); return fn.apply(context, argDefs.map(function(arg) { return argValues[arg]; })); }` and calling `argMapify(fn, {...})`, because you get the function from an outside vendor and you call the function on-the-fly. – ducin May 04 '15 at 12:57
  • 1
    Take care with using `node-introspect` because it is parsing a function as a string. Anything falling outside its regex (here in the future, we have ES6 single-argument arrow functions with optional brackets) will fail to parse. In short, take care if you're considering this in real-world usage. – davidjb Jan 03 '18 at 02:01
2

Without using any external libraries,

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

function call(method, object) {
    var params = getParamNames(method);
    var arrParams = [];
    for (var i = 0; i < params.length; i++) {
        arrParams.push(object[params[i]]);
    }
    return method.apply(arrParams);
}

Just call it with call(generateEmail, generateEmailObject).

Evan Knowles
  • 7,426
  • 2
  • 37
  • 71
0

Something like

   var orig_generateEmail = generateEmail;
   var generateEmail = function (map) {
              orig_generateEmail(map.firstName, map.lastname, map.provider);
   };

But that obviously ends up in a list of static "mappings".

Axel Amthor
  • 10,980
  • 1
  • 25
  • 44