Reversing an Object.entries conversion - javascript

I am using Object.entries in order to get some values out of a nested object and filter it.
obj = Object.entries(obj)
.filter(([k, v]) => {
return true; // some irrelevant conditions here
});
My object ends up as an array of arrays, of keys and vals.
[['key1', val]['key2', val]['key3', val]]
Is there a straightforward way to map these back into an object? The original object structure is:
{ key:val, key2:val2, key3:val3 }

Sure, just use .reduce to assign to a new object:
const input = { key:'val', key2:'val2', key3:'val3' };
const output = Object.entries(input)
.filter(([k, v]) => {
return true; // some irrelevant conditions here
})
.reduce((accum, [k, v]) => {
accum[k] = v;
return accum;
}, {});
console.log(output);
In modern browsers, you can also use Object.fromEntries which makes this even easier - you can just pass an array of entries, and it'll create the object from those entries.
const input = { key:'val', key2:'val2', key3:'val3' };
const output = Object.fromEntries(
Object.entries(input)
.filter(([k, v]) => {
return true; // some irrelevant conditions here
})
);
console.log(output);

For new browsers, use Object.fromEntries:
Object.fromEntries(arr);
For older js, it can still be a one liner.
arr.reduce((acc,[k,v])=>(acc[k]=v,acc),{})
Example:
Object.entries(sampleObject) // Turn object to array
.reduce((acc,[k,v])=>(acc[k]=v,acc),{}) // Turn it back to object.

Using Object.assign with a map that maps [k,v] => {[k]: v}
For example, the code below will only keep keys beginning with key
var obj = {
key: 1,
key2: 2,
key3: 3,
removed: 4,
alsoRemoved: 5
}
obj = Object.assign({}, ...Object.entries(obj)
.filter(([k, v]) => {
return k.startsWith('key');
})
.map(([k, v]) => ({[k]: v}))
);
console.log(obj);

Using reduce with deconstruction and comma operator:
const input = { key:'val', key2:'val2', key3:'val3' };
const output = Object.entries(input)
.filter(([k, v]) => {
return true; // some irrelevant conditions here
})
.reduce((acc, [k, v]) => (acc[k] = v, acc), {});
which should give the same functionality as CertainPerformance's answer with a bit more concise syntax

let entries = Object.entries({e: 'e', q: 'q'});
let reverse = entries.map(([t, r]) => ({[t]: r})).reduce((pv, cv) =>{return Object.assign(pv, cv)});
console.log(reverse);

If you know exactly which entries you want to exclude, you can use object deconstruction combined with spreading:
function clean(obj) {
const { unwanted1, unwanted2, ...wanted } = obj;
return { ...wanted };
}
For some cases, this might be the cleanest solution.

function undoEntries(entered){
let output = {};
entered.forEach(item => {
output[item[0]] = item[1]
});
return output;
};
// Example
const obj = { a: 1, b: 2, c: 3};
const input = Object.entries(obj);
const output = undoEntries(input);
console.log(output);

Related

How to iterate over object

I have an object like this:
const params = {
'userId[]': [1, 2],
'empId': 2,
advance: 'hello',
};
and I want to make a string like this:
userId[]=1&userId[]=2&empId=2&advance=hello
I am trying like this:
const myResult = Object.entries(params)
.map(([key, value]) => {
if (Array.isArray(value)) {
let result;
value.forEach(item => {
`${key}=${value}`
})
return result
}
// return
})
.join('&');
console.log(myResult);
but not able to figure out what to do next.
With a few changes, adding concatenation and non-array values:
const params = {
"userId[]": [1, 2],
empId: 2,
advance: "hello",
}
const myResult = Object.entries(params)
.map(([key, value]) => {
// Assume non-array result as default
let result = `${key}=${value}`
if (Array.isArray(value)) {
// Override for arrays
const r = value
.map((item) => {
return `${key}=${item}`
})
.join("&")
result = r
}
return result
})
.join("&")
console.log(myResult)
URLSearchParams can do most of the work for us, but we still have to handle the array ourselves:
const params = {
'userId[]': [1, 2],
'empId': 2,
advance: 'hello',
};
const p = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
for (const item of value) {
p.append(key, item);
}
} else p.append(key, value);
}
console.log(p.toString());
Looping through the entries of our object, we check if the value is an array. If it isn't we append it, otherwise, we append all its items. URLSearchParams makes the string for us; all we do is tell it what to use for the string :)
Note: "%5b%5D" is the same as "[]" but it's URI encoded. It should be treated the same by the server.
You can do the following instead of iterating over the object:
url = Object.keys(params).map(function(k) {return k + '=' + params[k]}).join('&')"
which would give you
'userId[]=1,2&empId=2&advance=hello'
I think you need this(guessing that you made a typo error)

Javascript groupBy implementation only produces 1 group

I am trying to implement my own groupBy method and, everything I see says this should work, but I only get 1 group when I use it with an array, even though the grouping is fine. What am I missing:
const merge = (array) => array.reduce((a, b) => Object.keys(a).map(key => {
return {
[key]: a[key].concat(b[key] || [])
};
}).reduce(((a,b) => Object.assign({},a,b))))
Array.prototype.groupBy = function (grouper) {
const groups = this.map(e => {
return {
[grouper(e)]: [e]
};
})
console.log("Groups:\n",JSON.stringify(groups))
return merge(groups)
}
const one = {
1: [1,2,3],
0: [4,5,6]
}
const two = {
1: [7,8,9],
0: [10,11,12]
}
const three = {
1: [13],
0: [16]
}
const array1 = merge([one,two,three])
console.log("case1:\n",JSON.stringify(array1,null,4))
const array2 = [1,2,3,4,5,6,7,9,10].groupBy(e => e % 2)
console.log("case2:\n",JSON.stringify(array2,null,4))
Outputs below, expected is 'case1':
case1:
{
"0": [
4,
5,
6,
10,
11,
12,
16
],
"1": [
1,
2,
3,
7,
8,
9,
13
]
}
Groups:
[{"1":[1]},{"0":[2]},{"1":[3]},{"0":[4]},{"1":[5]},{"0":[6]},{"1":[7]},{"1":[9]},{"0":[10]}]
case2:
{
"1": [
1,
3,
5,
7,
9
]
}
The first reduce in your merge method has a dependency on the keys of the first object in the array.
objs.reduce((a, b) => Object
.keys(a)
// ^-- Takes only the keys from `a`
.map(key => ({ [key]: a[key].concat(b[key] || []) })
// ^^^^^^-- only merges in those keys from `b`
)
To see the issue in action, take away the 0 or 1 key from your one object.
To fix it without deviating from your current approach too much, you could make sure you take both keys from a and b:
objs.reduce((a, b) => Object
.keys(Object.assign({}, a, b))
// etc...
)
It still feels a bit wasteful to first map to key-value-pair type objects and then merge those.
Final solution (removes another bug):
Array.prototype.groupBy = function (grouper) {
const keysOf = (...objs) => Object.keys(Object.assign({}, objs))
const groups = this.map(e => {
return {
[grouper(e)]: [e]
};
})
const merge = (array) => array.reduce((a, b) =>
keysOf(a, b).map(key => {
return {
[key]: (a[key] || []).concat(b[key] || [])
};
}).reduce((a, b) => Object.assign({}, a, b)))
return merge(groups)
}
const array2 = [1,2,3,4,5,6,7,9,10].groupBy(e => e % 2)
console.log("case2:\n",JSON.stringify(array2,null,2))
#user3297291 points out the issue. I would recommend a different merge altogether. First we write merge2 helper which destructively merges b into a -
function merge2 (a, b)
{ for (const [k, v] of Object.entries(b))
if (a[k])
a[k] = [ ...a[k], ...v]
else
a[k] = v
return a
}
Now you can write merge to accept any number of objects. Since it initialises the reduce with a fresh {}, no input objects will be mutated -
const merge = (...all) =>
all.reduce(merge2, {})
Now groupBy works the way you write it, simply applying the mapped elements to merge -
const groupBy = (arr, f) =>
merge(...arr.map(v => ({ [f(v)]: [v] })))
const result =
groupBy([1,2,3,4,5,6,7,9,10], e => e % 2)
Expand the snippet below to verify the result in your own browser -
function merge2 (a, b)
{ for (const [k, v] of Object.entries(b))
if (a[k])
a[k] = [ ...a[k], ...v]
else
a[k] = v
return a
}
const merge = (...all) =>
all.reduce(merge2, {})
const groupBy = (arr, f) =>
merge(...arr.map(v => ({ [f(v)]: [v] })))
const result =
groupBy([1,2,3,4,5,6,7,9,10], e => e % 2)
console.log(JSON.stringify(result))
{"0":[2,4,6,10],"1":[1,3,5,7,9]}
If you want to make merge2 using a pure functional expression, you can write it as -
const merge2 = (a, b) =>
Object
.entries(b)
.reduce
( (r, [k, v]) =>
r[k]
? Object.assign(r, { [k]: [...r[k], ...v] })
: Object.assign(r, { [k]: v })
, a
)
You could skip the whole merge song and dance and write groupBy in a more direct way -
const call = (f, v) =>
f(v)
const groupBy = (arr, f) =>
arr.reduce
( (r, v) =>
call
( k =>
({ ...r, [k]: r[k] ? [...r[k], v] : [v] })
, f(v)
)
, {}
)
const result =
groupBy([1,2,3,4,5,6,7,9,10], e => e % 2)
console.log(JSON.stringify(result))
{"0":[2,4,6,10],"1":[1,3,5,7,9]}
Another option is to use Map as it was designed, and convert to an Object after -
const call = (f, v) =>
f(v)
const groupBy = (arr, f) =>
call
( m =>
Array.from
( m.entries()
, ([ k, v ]) => ({ [k]: v })
)
, arr.reduce
( (r, v) =>
call
( k =>
r.set
( k
, r.has(k)
? r.get(k).concat([v])
: [v]
)
, f(v)
)
, new Map
)
)
const result =
groupBy([1,2,3,4,5,6,7,9,10], e => e % 2)
console.log(JSON.stringify(result))

Invert key value in js object

I can't figure out how I can change :
{"first":["de"], "second":["ab","de"], "third":["de"]}
to:
{"de":["first", "second", "third"], "ab":["second"]}
I want to associate unique values with list of containing keys. What I tried(but I think I'm far from it):
const data = {
"first":["de"],
"second":["ab","de"],
"third":["de"]
}
console.log(
Object
.keys(data).reduce(function(obj, key) {
obj[data[key]] = key;
return obj;
}, {})
)
Thanks for your help!
Object.entries to get it into an array, reduce to build the new object, and forEach to loop over the array
const o = {"first":["de"], "second":["ab","de"], "third":["de"]}
const result = Object.entries(o).reduce((obj, [key, arr])=>{
arr.forEach(lng => {
obj[lng] = obj[lng] || [];
obj[lng].push(key);
})
return obj
}, {});
console.log(result);
You have to loop the array and for each item in the array check if an array for that value exists in the accumulator or not before adding it:
let result = Object.entries(data).reduce((acc, [key, arr]) => { // for each key-array of the original object
arr.forEach(value => { // for each value in the array
acc[value] = acc[value] || []; // create an array in the output object if it doesn't already exist
acc[value].push(key); // push the key to it
});
return acc;
}, {});
I also used Object.entries with each entry desctuctured as [key, arr] so I don't have to use the extra [key] to get the array while using Object.keys.
Demo:
let data = {"first":["de"], "second":["ab","de"], "third":["de"]};
let result = Object.entries(data).reduce((acc, [key, arr]) => {
arr.forEach(value => {
acc[value] = acc[value] || [];
acc[value].push(key);
});
return acc;
}, {});
console.log(result);
On reduce callback, data[key] is an array of string values. So it is needed to loop that data[key] array values and assign value for each array item.
const data = {
"first":["de"],
"second":["ab","de"],
"third":["de"]
}
console.log(
Object.keys(data).reduce(function(obj, key) {
data[key].forEach((val) => {
obj[val] ? obj[val].push(key) : obj[val] = [ key ];
});
return obj;
}, {})
)
Try this (naive solution), if this works for you
const data = { first: ["de"], second: ["ab", "de"], third: ["de"] };
let dataMap = new Map();
Object.keys(data).forEach((key) => {
data[key].forEach((val) => {
if (dataMap.has(val)) {
dataMap.set(val, [...dataMap.get(val), key]);
} else {
dataMap.set(val, [key]);
}
});
});
let nData = [];
dataMap.forEach((value, key) => {
nData.push({
[key]: value
});
});
console.log(nData);
You could take a double reduce with the entries.
const
data = { first: ["de"], second: ["ab", "de"], third: ["de"] },
result = Object
.entries(data)
.reduce((o, [value, keys]) => keys.reduce((q, key) => {
(q[key] ??= []).push(value);
return q;
}, o), {});
console.log(result);
I'm not using reduce but here's a "bruteforce" for your problem which works:
res = {};
Object.keys(data).forEach(key => {
data[key].forEach(el => {
if (! res[el])
res[el] = [];
if (! res[el].includes(key))
res[el].push(key);
})
});

convert object of array to object javascript

I have an object includes arrays of arrays like below
objArray= { hh:[['a','b'],['c','d']],jj:[['x','y'],['z','w']]}
and what I want is here to change array to object:
convertedObject
{
hh:[
{id:'a', name:'b'},
{id:'c', name:'d'}
],
jj:[
{id:'x', name:'y'},
{id:'z', name:'w'}
],
}
I wrote 2 functions to do this but it needs a little change
function convertToObjects(arr) {
return Object.values(arr).map(e => {
return { id: e[0], name: e[1] };
});
}
function convertChoicesToObjects(choicesArray) {
return Object.keys(choicesArray).map((key) => (convertToObjects(choicesArray[key])));
}
const convertedObject=convertChoicesToObjects(objArray)
My function output is:
{
0:[
{id:'a', name:'b'},
{id:'c', name:'d'}
],
1:[
{id:'x', name:'y'},
{id:'z', name:'w'}
],
}
iterate over keys and use map
const objArray = {"hh": [["a", "b"], ["c", "d"]], "jj": [["x", "y"], ["z", "w"]]};
const output = {};
Object.keys(objArray).forEach(key => {
output[key] = objArray[key].map(item => ({"id": item[0], "name": item[1]}));
});
console.log(output);
You could use map and forEach methods.
objArray= { a:[['a','b'],['c','d']],b:[['x','y'],['z','w']]}
Object.keys(objArray).forEach((key) => {
objArray[key] = objArray[key].map(([id, name]) => ({id, name}));
});
console.log(objArray);
The output can be acheived using a simple for...in loop and using .map() method of arrays:
const input = {
a: [['a', 'b'], ['c', 'd']],
b: [['x', 'y'],['z', 'w']]
};
const transform = (input) => {
const output = {};
for (key in input) {
output[key] = input[key].map(([id, name]) => ({id, name}));
}
return output;
};
console.log(transform(input));
You can use reduce()
const objArray = { a:[['a','b'],['c','d']],b:[['x','y'],['z','w']]};
const data = Object.keys(objArray).reduce((prev, key) => {
prev[key] = objArray[key].reduce((res, arr) => {
res.push({id: arr[0], name: arr[1] });
return res;
}, []);
return prev;
}, {});
console.log(data);
You could build new object with Object.fromEntries.
This approach uses another array for the wanted keys of the objects.
var data = { a: [['a', 'b'],['c','d']],b:[['x','y'],['z','w']]},
keys = ['id', 'name'],
result = Object.fromEntries(
Object
.entries(data)
.map(([k, v]) => [
k,
v.map(a => Object.fromEntries(keys.map((k, i) => [k, a[i]])))
])
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can write programs like little stories using prgm -
const myConvert = (o = {}) =>
prgm // given input object, o
( o // starting with o
, Object.entries // get its entries
, map (convert1) // then map over them using convert1
, Object.fromEntries // then make a new object
)
const convert1 = ([ key, values ]) =>
prgm // given input key and values
( values // starting with values
, map (([ id, name ]) => ({ id, name })) // map over arrays creating objs
, r => [ key, r ] // then create a key/result pair
)
const input =
{ a: [ ['a','b']
, ['c','d']
]
, b: [ ['x','y']
, ['z','w']
]
}
console.log
( myConvert (input)
)
// => { ... }
To make this possible, we need -
const prgm = (x, ...fs) =>
fs .reduce ((r, f) => f (r), x)
const map = f => xs =>
xs .map (x => f (x))
But perhaps a better name for myConvert is objectMap. To make it generic, we will make the conversion function convert1 a parameter. And since there's no need to modify the keys of the input object, we will only call the conversion function on the object's values -
const identity = x =>
x
const objectMap = (f = identity) => (o = {}) =>
prgm // given mapper, f, and object, o
( o // starting with o
, Object.entries // get its entries
, map (([ k, v ]) => [ k, f (v) ]) // transform each v using f
, Object.fromEntries // then make a new object
)
Now using generic function objectMap, we can write myConvert as a specialization. This isolates the unique essence of your transformation and detangles it from the rest of your program -
const myConvert =
objectMap // using generic objectMap
( map (([ id, name ]) => ({ id, name })) // convert arrays to objects
)
const input =
{ a: [ ['a','b']
, ['c','d']
]
, b: [ ['x','y']
, ['z','w']
]
}
console.log
( myConvert (input)
)
// => { ... }
Hopefully this shows the power of thinking about your programs from different perspectives. Run the snippet below to confirm the results in your browser -
const prgm = (x, ...fs) =>
fs .reduce ((r, f) => f (r), x)
const map = f => xs =>
xs .map (x => f (x))
const identity = x =>
x
const objectMap = (f = identity) => (o = {}) =>
prgm
( o
, Object.entries
, map (([ k, v ]) => [ k, f (v) ])
, Object.fromEntries
)
// ---
const myConvert =
objectMap
( map (([ id, name ]) => ({ id, name }))
)
const input =
{ a: [ ['a','b']
, ['c','d']
]
, b: [ ['x','y']
, ['z','w']
]
}
console.log
( myConvert (input)
)

converting lodash's pickby in to javascript

how can I make lodash's pickby function in javascript? I have found the following one in "you don't need lodash"
function pickBy(object) {
const obj = {};
for (const key in object) {
if (object[key] !== null && object[key] !== false && object[key] !== undefined) {
obj[key] = object[key];
}
}
return obj;
}
but wondering other implementations
You could add a predicate function, as _.pickBy describes and use the entries and filter the data and build a new object.
function pickBy(object, predicate = v => v) {
return Object.assign(
...Object
.entries(object)
.filter(([, v]) => predicate(v))
.map(([k, v]) => ({ [k]: v }))
);
}
To create _.pickBy(), you can use for...of with Object.entries(). If the predicate returns a truthy answer for the value, assign the key and value to the result object.
Note: if you need _.pickBy(), and you don't want to entire lodash package, you can import the pickBy module.
function pickBy(object, predicate = v => v) {
const obj = {};
for (const [key, value] of Object.entries(object)) {
if (predicate(value)) obj[key] = value;
}
return obj;
}
console.log(pickBy({ a: 1, b: 0, c: 3 }));
console.log(pickBy({ a: 1, b: 0, c: 3 }, v => v < 3 ));
Now days you can get the entries, filter by the value, and convert back to an object with Object.fromEntries():
const pickBy = (object, predicate = v => v) =>
Object.fromEntries(Object.entries(object).filter(([, v]) => predicate(v)))
console.log(pickBy({ a: 1, b: 0, c: 3 }));
console.log(pickBy({ a: 1, b: 0, c: 3 }, v => v < 3 ));
Being
var object = { 'a': 1, 'b': '2', 'c': 3 };
and callback a method which is applied just to the values of the object (_.isNumber):
Object.keys(obj).reduce((prev, x) => callback(obj[x]) ? { ...prev, [x] : obj[x] } : prev , {});
Nina's solution fails when the object is {}.
Here is mine:
const pickBy = (object, predicate = v => v) =>
Object.entries(object)
.filter(([k, v]) => predicate(v))
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});

Categories