简体   繁体   中英

Mapping an object to array of objects in ES6

How would I convert a object to an array of objects while keeping key names?

// actual 
obj = {
  key1: null,
  key2: "Nelly",
  key3: [ "suit", "sweat" ]
} 

// expected 
arr = [
  { key2: "Nelly" },
  { key3: [ "suit", "sweat" ] }
]

currently my solution is...

 var arr = Object.keys(obj).map(key => { if (obj[key]) return { key: obj[key] } });

which returns

arr = [
  undefined,
  { key: "Nelly" },
  { key: [ "suit", "sweat" ] }
]

Transducers

There's heaps of answers here to help you reach your answer in a practical way – filter this, map that, and voilà, the result you're looking for. There's other answers using primitive for loops, but those make you sad.

So you're wondering, "is it possible to filter and map without iterating through the array more than once?" Yes, using transducers .


Runnable demo

I might update this paragraph with more code explanation if necessary. ES6 comin' at you …

 // Trans monoid const Trans = f => ({ runTrans: f, concat: ({runTrans: g}) => Trans(k => f(g(k))) }) Trans.empty = () => Trans(k => k) const transduce = (t, m, i) => i.reduce(t.runTrans((acc, x) => acc.concat(x)), m.empty()) // complete Array monoid implementation Array.empty = () => [] // transducers const mapper = f => Trans(k => (acc, x) => k(acc, f(x))) const filterer = f => Trans(k => (acc, x) => f(x) ? k(acc, x) : acc) const logger = label => Trans(k => (acc, x) => (console.log(label, x), k(acc, x))) // your function, implemented with transducers const foo = o => { const t = logger('filtering') .concat(filterer(k => o[k] !== null)) .concat(logger('mapping')) .concat(mapper(k => ({ [k]: o[k] }))) .concat(logger('result')) return transduce(t, Array, Object.keys(o)) } console.log(foo({a: null, b: 2, c: 3})) 

Output; notice the steps appear interlaced – filtering , mapping , result , repeat – this means each of the combined transducers run for each iteration of the input array. Also notice how because a 's value is null , there is no mapping or result step for a ; it skips right to filtering b – all of this means we only stepped thru the array once .

// filtering a
// filtering b
// mapping b
// result { b: 2 }
// filtering c
// mapping c
// result { c: 3 }
// => [ { b: 2 }, { c: 3 } ]

Finishing up

Of course that foo function has lots of console.log stuff tho. In case it's not obvious, we just want to remove the logger transducers for our actual implementation

const foo = o => {
  const t = filterer(k => o[k] !== null)
    .concat(mapper(k => ({ [k]: o[k] })))
  return transduce(t, Array, Object.keys(o))
}

console.log(foo({a: null, b: 2, c: 3}))
// => [ {b: 2}, {c: 3} ]

Attribution

My enlightenment on the subject is owed exclusively to Brian Lonsdorf and accompanying work: Monoidal Contravariant Functors Are Actually Useful

.map() returns an array of the same length as the original array. Code like yours with a callback that doesn't return a value in some cases will result in elements with the value undefined . One way to deal with that is to first .filter() out the elements you don't want to keep.

Anyway, to get the key names you want you can use an object literal with a computed property name :

{ [key]: obj[key] }

In context:

 const obj = { key1: null, key2: 'Nelly', key3: [ 'suit', 'sweat' ] } const arr = Object.keys(obj) .filter(v => obj[v] != null) .map(key => ({ [key]: obj[key] })) console.log(arr) 

As @zerkms says, I don't think using multiple es6 functions is going to improve your code. Try a loop!

// actual 
let obj = {
  key1: null,
  key2: "Nelly",
  key3: [ "suit", "sweat" ]
};

let arr = [];
let k = Object.keys(obj);

for(let i = 0, len = k.length; i < len; i++) {
  let key = k[i];
  if (obj[key]) {
    arr.push({key: obj[key]});
  }
}

If you use map , the length of your expected array will be the same as the number of keys in your input. So map is not appropriate in this case. My solution is to use a reduce function like so:

 var obj = { key1: null, key2: 'Nelly', key3: [ 'suit', 'sweat' ] } var res = Object.keys(obj).reduce( (acc, curr) => { // if current key's value is not null // insert object to the resulting array acc if (obj[curr]) { acc.push({[curr] : obj[curr]}); return acc; } // if they key value is null, skip it return acc; }, [] ); console.log(res); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM