Search code examples
javascriptlodash

idiomatic use of pickBy with object literal instead of function predicate


There appears to be an idiomatic use of lodash pickBy utility function whereby instead of supplying a boolean predicate function as the second argument, an object literal is supplied instead. E.g. https://stackoverflow.com/a/51744183/274677

Animals = [{Name: 'Dog', Id: 0},
      {Name: 'Cat', Id: 1},
      {Name: 'Mouse', Id: 2},
      {Name: 'Horse', Id: 3},
      {Name: 'Pig', Id: 3}]
Indexes = _.keys(_.pickBy(Animals, {Id: 3}))

output:

Indexes = ["3", "4"]

This use is undocumented in lodash: https://lodash.com/docs/4.17.15#pickBy

What is the origin of this idiom and why does it work?


Solution

  • Why does it work?

    Looking at the compiled lodash's code we can see that the pickBy looks like that:

    function pickBy(object, predicate) {
      if (object == null) {
        return {};
      }
      var props = arrayMap(getAllKeysIn(object), function(prop) {
        return [prop];
      });
      predicate = getIteratee(predicate);
      return basePickBy(object, props, function(value, path) {
        return predicate(value, path[0]);
      });
    }
    

    predicate (in our example {Id: 3}) is passed to the getIteratee function which looks like that:

    function getIteratee() {
      var result = lodash.iteratee || iteratee;
      result = result === iteratee ? baseIteratee : result;
      return arguments.length ? result(arguments[0], arguments[1]) : result;
    }
    

    Now, looking at the iteratee:

    function iteratee(func) {
      return baseIteratee(typeof func == 'function' ? func : baseClone(func, CLONE_DEEP_FLAG));
    }
    

    And baseIteratee:

    function baseIteratee(value) {
      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
      if (typeof value == 'function') {
        return value;
      }
      if (value == null) {
        return identity;
      }
      if (typeof value == 'object') {
        return isArray(value)
          ? baseMatchesProperty(value[0], value[1])
          : baseMatches(value);
      }
      return property(value);
    }
    

    We know that our argument is an object, but not an array, so few more functions deeper we can find the actual function that is returned:

    function getMatchData(object) {
      var result = keys(object),
        length = result.length;
    
      while (length--) {
        var key = result[length],
          value = object[key];
    
        result[length] = [key, value, isStrictComparable(value)];
      }
      return result;
    }
    

    which just goes through an array and checks if the value is equal

    Why is it not documented?

    I have no idea, sorry