Reputation: 659
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" ] }
]
Upvotes: 6
Views: 9553
Reputation: 135416
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
Upvotes: 7
Reputation: 150070
.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)
Upvotes: 14
Reputation: 8552
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);
Upvotes: 1
Reputation: 2841
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]});
}
}
Upvotes: 3