13

I want to get the first n key/value pairs from an object (not an array) using lodash. I found this answer for underscore, which says to use use first (doesn't exist in lodash), or to use take (only works on arrays).

Sample node session trying to get the 'a:7' and 'b:8' pairs from an object:

> var ld=require("lodash")
undefined
> var o={a:7, b:8, c:9}
undefined
> ld.keys(o)
[ 'a', 'b', 'c' ]
> ld.take(o, 2)
[]
> ld.first(o, 2)
undefined
> 

Surely, there must be some easy way to do this with lodash, but for the life of me I can't find anything. Maybe I have to resort to native js?

Community
  • 1
  • 1
vt5491
  • 1,964
  • 2
  • 17
  • 28
  • What is your expected output? [ a, b ] ? – Samuel Toh Sep 14 '16 at 06:25
  • I would expect to get {a: 7, b: 8} -- a "hash" (object) with a subset of the original. I'm more or less treating it as a (perl-like) hash. – vt5491 Sep 14 '16 at 06:35
  • 4
    Something keep in mind - the "first" key in an object is completely meaningless. There is no required consistency of object properties, so the "first three" could (in theory) be any three. Browsers usually return properties in insertion order but don't rely on that behaviour. – VLAZ Sep 14 '16 at 06:39
  • @vt5491 Updated my solution to reflect the new expected output – Samuel Toh Sep 14 '16 at 06:39

3 Answers3

7

You cannot take the first N elements of an object without writing custom code. This is because there is no ordering of the elements in objects, so if there were a library function for it, it would never be guaranteed to give you the elements you expect. Given an object like

var obj = {  b: 3, y: 2, a: 1 };

it is not clear what the "first 2" refers to - do you want a and b because that is the alphabetic order? If so are they in that order or not? If not, do you want b and y because they appear first? Perhaps you want a and y because of their values being the lowest?

There is no guarantee for what you will get aside from not getting duplicates, so all of those combinations are valid. Furthermore, you can get them in any order y and a is equally valid output You may prefer one or another but it doesn't make it correct in general.

There are ways around this and but you have to accept that you need to deal with the non-order.

Pure JavaScript solution.

function firstN(obj, n) {
  return Object.keys(obj) //get the keys out
    .sort() //this will ensure consistent ordering of what you will get back. If you want something in non-aphabetical order, you will need to supply a custom sorting function
    .slice(0, n) //get the first N
    .reduce(function(memo, current) { //generate a new object out of them
      memo[current] = obj[current]
      return memo;
    }, {})
}
var obj = { b: 2, y: 25, a: 1 }

console.log( firstN(obj, 2) );

This is using Object.keys, Array.prototype.sort, and Array.prototype.reduce

The same can be achieved with lodash but not vastly more concise than this - it would involve calling similar functionality. It can be done like this, for example:

function firstN(obj, n) {
  return _.chain(obj)
    .keys()
    .sort()
    .take(n)
    .reduce(function(memo, current) {
      memo[current] = obj[current];
      return memo;
    }, {})
    .value();
}

var obj = { b: 2, y: 25, a: 1 }
      
console.log( firstN(obj, 2) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

As you can see, it's pretty much the same as before. There can be variations on the syntax and the exact means of how you do this task, but the major points should still be there - you need to sort for consistency and then you can get any number of items.

Community
  • 1
  • 1
VLAZ
  • 18,437
  • 8
  • 35
  • 54
  • yes, I like this because it's in a functional style. – vt5491 Sep 14 '16 at 09:04
  • Still, I think I'm learning that lodash is biased toward arrays. It's ok to have hashes in arrays though e.g [{a:1}, {b:2}]. So maybe the ultimate solution is to avoid using hashes (objects) as containers. But I also discovered the 'object' section of lodash. I had some good luck with this after finding the *toPairs* function: `> object = {a: 7, b: 8, c: 9} { a: 7, b: 8, c: 9 } > _.chain(object).toPairs(o).take(2).value() [ [ 'a', 7 ], [ 'b', 8 ] ] – vt5491 Sep 14 '16 at 09:13
  • 1
    That's `object = {a: 7, b: 8, c: 9}; _.chain(object).toPairs().take(2).value()` returns: `[ [ 'a', 7 ], [ 'b', 8 ] ]` – vt5491 Sep 14 '16 at 09:26
  • 1
    Yes, you can certainly use `toPairs` if that suits you better. I find that a lot of times, I don't really need an object and an array with `[key, value]` is perfectly fine. If you DO need an object you can create an object from arrays of pairs. I can't remember the name of the function right now (because it's not the logical `fromPairs`...) but it's possible. So you could do the lodash code as `toPairs` then filter those and then join the pairs together into a new object. – VLAZ Sep 14 '16 at 10:28
4

If you look at the loadash documentation for first. It only takes in an array as its argument, and this is probably not the API to use.

See: https://lodash.com/docs/3.10.1#first


Here is 1 method you can solve it using standard Javascript API.

The catch here is that you can use the Object.keys(...)[index] API to retrieve the element's key based on their position.

Then all you need to do is just loop n number of times and using the derived key push it into another object.

var firstN = 2;
var o={a:7, b:8, c:9};
var result = {};

for (var index=0; index < firstN; index++) {
  var key = Object.keys(o)[index];
  result[key] = o[key];
}

console.log(result);
Samuel Toh
  • 14,302
  • 3
  • 19
  • 35
  • Thank you for the repsonse. So what your basically saying is lodash cannot do it. The whole reason I'm trying to use lodash is to avoid having to write boilerplate, and to start thinking "functionally". But I do appreciate the point about objects not being ordered etc. I'm not knocking your response and I'm grateful for it, but I do think it's disappointing (and kind of surprising) that lodash doesn't provide this functionality. Like I said, this is not directed at you, just a general observation about lodash. p.s. I still like lodash though. – vt5491 Sep 14 '16 at 06:56
  • @vt5491 - I see. You can still do what you wanted to using `take` See my updated answer. Hopefully that helps in some way. – Samuel Toh Sep 14 '16 at 07:16
  • 1
    @vt5491 - did a bit of research for you and found that this guy had similar issue as you as well :P https://github.com/lodash/lodash/issues/946 – Samuel Toh Sep 14 '16 at 07:21
  • @SamuelToh - `take` does not do exactly the same. It doesn't work on objects, so all you'll be able to do with it is extract keys (or key-value pairs as an array) and then `take` the first N of those, but you cannot directly do `_take(obj, 2)` and get an object that contains two (however defined) properties. – VLAZ Sep 14 '16 at 07:26
  • Also, that issue you linked to is completely different from OP's - it's about `first` not existing in lodash, whereas it does in Underscore. It has nothing to do with using `.take/.first` on objects. – VLAZ Sep 14 '16 at 07:28
  • @vlaz okay. I see I'lll update the solution and take away the `take` bit to make it less misleading when I got home later. Thanks for clarifying – Samuel Toh Sep 14 '16 at 07:30
0

There is no straight-forward solution. Inspired by previous answers and comments, and reading a couple of articles about non-guaranteed properties order, I created a solution where I sort the keys first.

Sorting the keys

Here is a usable one-line approach with sorted keys (and therefore guaranteed order).

Chaining

_.chain(object).toPairs().sortBy(0).take(2).fromPairs().value()

Without chaining

_.fromPairs(_.take(_.sortBy(_.toPairs(object), 0), 2)),

Details on sorting

The sortBy(0) sorts our collection by keys (index 0). The original object is at first converted by toPairs() to an array of pairs (each pair is an array [key, value]) and then sorted by the first values of these pairs (the key has index 0 in the pair).


Important: As mentioned in previous answers and comments, the order of properties cannot be guaranteed, even in the latest ES versions. See this updated SO answer. Therefore I am sorting the keys.

exmaxx
  • 2,166
  • 21
  • 23