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.