Group object by value type - javascript

I have this object:
const data = {
a: 'name',
b: 'age',
c: 'age',
d: 'age',
e: 'name',
f: 'age'
}
and I want to group by values so I would like to have:
const result = {
name: ['a', 'e'],
age: ['b', 'c', 'd', 'f']
}
Is there a smart way to do that?

Tersest version, thanks Nick for the comma operator to save the curlies
const data = { a: 'name', b: 'age', c: 'age', d: 'age', e: 'name', f: 'age' },
arr = Object.entries(data)
.reduce((acc, [key,value]) => ((acc[value] ??=[]).push(key), acc),{});
console.log(arr)
Explanations since this is interesting to add to one's arsenal
(acc, // accumulator defined by the {} at the end of the statement
[key,value] // destructing the entries
) =>
( // bracket to set up the comma operator later
(acc[value] ??=[]) // nullish coalescing assignment - if no acc[value] then assign an array
.push(key)
, acc) // comma operator returns what is after the comma
,{});

You can iterate over all the keys and check if the corresponding value has been already assigned as key to your result object; if so, push the key into the array, if not initialise it as an array.
Something like this will work:
const data = { a: 'name', b: 'age', c: 'age', d: 'age', e: 'name', f: 'age' }
const output = {};
Object.keys(data).forEach(key => {
if (!output[data[key]]) {
// new key: assign it as a 1 element array
output[data[key]] = [key];
} else {
// existing key: push it into the array
output[data[key]].push(key);
}
});
console.log(output);

Some good solutions added, but could be more clear with Object.entries ?
const data = { a: 'name', b: 'age', c: 'age', d: 'age', e: 'name', f: 'age' }
const results = {};
Object.entries(data).forEach(([key, value]) => {
if (results[value]) {
results[value].push(key);
} else {
results[value] = [key];
}
});
console.log(results)

One way to do so:
const map = new Map;
Object.keys(data).forEach(key => {
const value = data[key];
if (!map.has(value))
map.set(value, []);
map.get(value).push(key);
});
const result = {};
map.forEach((key, value) => {
result[value] = key;
});
console.log(result);

Another way:
const data = { a: 'name', b: 'age', c: 'age', d: 'age', e: 'name', f: 'age' }
const newData = {};
Object.entries(data).forEach(pair => {
const [key, value] = pair;
if (!newData[value])
newData[value] = [];
newData[value].push(key)
});
console.log(newData)

Related

Compare two objects and replace values with same key - keep/ignore the rest

I am try merge two objects, but no really merge. I would like to only replace the values of the keys in common - ignore the rest.
obj1 = { a: 'replace a', b: 'keep b' }
obj2 = { a: 'new value a', c: 'ignore c' }
expected result:
console.log(obj1)
// { a: 'new value a', b: 'keep b' }
I tried a couple different things like:
for (const [k, v] of Object.entries(obj2)) {
obj1[k] = v
}
but not really what I was going for. And keep getting this Type error:
Element implicitly has an 'any' type because expression of type
'string' can't be used to index type ... No index signature with a
parameter of type 'string' was found on type ...
You could iterate the keys of obj2 and check if the key exists in obj1, then update with value.
const
obj1 = { a: 'replace a', b: 'keep b' },
obj2 = { a: 'new value a', c: 'ignore c' };
for (const key in obj2) if (key in obj1) obj1[key] = obj2[key];
console.log(obj1);
You can use this approach.
obj1 = { a: 'replace a', b: 'keep b' }
obj2 = { a: 'new value a', c: 'ignore c' }
newObj = {}
for (const key in obj1) {
if (Object.hasOwnProperty.call(obj1, key)) {
const value = obj1[key];
newObj[ key ] = value;
if( Object.hasOwnProperty.call(obj2, key) ){
const value2 = obj2[key];
newObj[ key ] = value2;
}
}
}
console.log( newObj )
Loop over the object with the new values and update the values of the original object if the key is already present.
const obj1 = { a: 'replace a', b: 'keep b' };
const obj2 = { a: 'new value a', c: 'ignore c' };
const mergeObjects = (obj1, obj2) => {
// Create new object to prevent overwriting the original.
const merged = Object.assign({}, obj1);
for (const key of Object.keys(obj2)) {
if (key in merged) {
merged[key] = obj2[key];
}
};
return merged;
}
const result = mergeObjects(obj1, obj2);
console.log(result);

Deleting key to nested object?

In this question I got some excellent answers, but it turned out I had over simplified the problem I am facing.
I have the same object
const obj = {
a: 'A',
b: {
bb: 'BB',
bbb: 'BBB',
},
c: 'C'
};
and I somehow need to end up with
{ a: 'A', bb: 'BB', bbb: 'BBB', c: 'C' }
where there key to the nested object is removed. It doesn't have to be in place. Creating a new object is fine.
Question
Can anyone figure out how to delete the key from the nested object, but still keep the nested object?
You can flatten it recursively:
function flattenObject(obj) {
const ret = {};
for (const [key, val] of Object.entries(obj)) {
if (typeof val === "object") {
Object.assign(ret, flattenObject(val));
} else {
ret[key] = val;
}
}
return ret;
}
Get an array of [key, value] pairs using Object.entries(). Iterate the pairs with Array.flatMap(). If the value is object, call the function with the value. If not return an object of { [key]: value }. Merge to a single object by spreading the array of object into Object.assign().
Use recursion and merge the sub objects by spreading into Object.assign():
const fn = obj => Object.assign({},
...Object.entries(obj)
.flatMap(([k, v]) => typeof(v) === "object" ? fn(v) : { [k]: v })
);
const j = { a: 'A', b: { bb: 'BB', bbb: 'BBB' }, c: 'C' };
const result = fn(j);
console.log(result);
Another option is to work directly with the [key, value] entries (p), and then convert everything to a single object using Object.fromEntries():
const fn = obj =>
Object.entries(obj)
.flatMap(p => typeof(p[1]) === "object" ? fn(p[1]) : [p])
const j = { a: 'A', b: { bb: 'BB', bbb: 'BBB' }, c: 'C' };
const result = Object.fromEntries(fn(j));
console.log(result);

ES6 spread syntax : skip empty keys automatically?

What is simpler pattern to avoid updating inputs if one of the keys is empty in payload ?
Is there a nice ES6 syntax ?
const master = {
inputs: {a: [], b: [], c: []}
};
const {a, b, c} = payload;
const updateMaster = (payload) => ({
...master, inputs: {...master.inputs, ...payload}
});
To filter the fields of an object, use Object.entries to retrieve the fields, Array.prototype.filter to filter then, and Object.formEntries to reconstruct an object from the filtered entries.
let payload = {
a: [],
b: [1, 2]
};
let nonEmptyPayload = Object.fromEntries(Object.entries(payload).filter(([_, v]) => v.length))
console.log(nonEmptyPayload);
Applying this to your example,
let master = {
inputs: {
a: [],
b: [13, 14],
c: [10, 12]
}
};
let trimObj = obj => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v.length));
let updateMaster = payload => ({
...master,
inputs: { ...master.inputs,
...trimObj(payload)
}
});
updateMaster({
b: [15, 16], // Will override master.c
c: [] // Will not override master.c
});
console.log(master);
You could create a function like this. It removes all empty values from an object, without directly modifying the object passed to the function.
const removeEmpty = obj => {
return Object.keys(obj).reduce((acc, key) => {
// value is "falsey" or is empty array
return !obj[key] || (Array.isArray(obj[key]) && !obj[key].length)
? acc
: {...acc, [key]: obj[key]}
}, {})
}
console.log(removeEmpty({a: 'AAA', b: '', c: 'CCC', d: false, e: null, f: [1,2], g: []}))
So your final snippet would look like this:
const updateMaster = (payload) => ({
...master, inputs: {...master.inputs, ...removeEmpty(payload)}
});

Fill fields in object, from other object

In Javascript (ES6) have two objects with data.
const template = {
a: '',
b: '',
x: ''
}
This is the data object I will receive
const data = {
a: 'test',
b: 'test',
c: 'test'
}
How can I map the data from my received object to my template object without allowing values that are not present in the template object.
So the result should be this.
const result = {
a: 'test',
b: 'test',
x: ''
}
you can use a for...in loop :
const template = {
a: '',
b: ''
}
const data = {
a: 'test',
b: 'test',
c: 'test'
}
const result = {};
for (let k in template) {
result[k] = data[k];
}
console.log(result)
You could get the template object and the properties with the same keys from data.
const
template = { a: '', b: '', x: '' },
data = { a: 'test', b: 'test', c: 'test' },
result = Object.assign(
{},
template,
...Object.keys(template).map(k => k in data && { [k]: data[k] })
);
console.log(result);
Something like this:
let result = {};
const template = {
a: '',
b: ''
}
const data = {
a: 'test',
b: 'test',
c: 'test'
}
for (let prop in data) {
if(prop in template) result[prop] = data[prop];
}
console.log(result);
Just for fun you could use some Proxy magic :)
const template = {
a: '',
b: ''
}
const data = {
a: 'test',
b: 'test',
c: 'test'
}
const result = { ...new Proxy(data, {
ownKeys: () => Object.keys(template)
})
}
console.log(result)
const template = {
a: '',
b: ''
}
const data = {
a: 'test',
b: 'test',
c: 'test'
}
function setData(inputTemplate, inputData) {
outputObject = {}
for (var key in inputTemplate) {
if (inputData[key]) {
outputObject[key] = inputData[key];
}
}
return outputObject
}
console.log(setData(template, data))
You can use of Array.reduce to look at your object and only change the keys you are interested in. This method will also handle the case where the key you want to copy from data is not here. Also we have created a new object, we do not mutate the existing one.
Without mutation (new object)
const template = {
a: '',
b: '',
};
const data = {
a: 'test',
b: 'test',
c: 'test',
};
const ret = Object.keys(template).reduce((tmp, x) => {
tmp[x] = data[x] !== void 0 ? data[x] : tmp[x];
return tmp;
}, {
...template,
});
console.log(ret);
Mutating the object (use the old object)
const template = {
a: '',
b: '',
};
const data = {
a: 'test',
b: 'test',
c: 'test',
};
Object.keys(template).forEach((x) => {
template[x] = data[x] !== void 0 ? data[x] : template[x];
});
console.log(template);
You can simply loop on the keys of your template, and set the value with the data object with the same key.
const data = {
a: 'test',
b: 'test',
c: 'test'
}
const template = {
a: '',
b: ''
}
Object.keys(template).forEach((key) => template[key] = data[key])
console.log(template)
you can also use reduce
Example :
const template = {
a: '',
b: ''
}
const data = {
a: 'test',
b: 'test',
c: 'test'
}
const res = Object.keys(template).reduce((all, acc) => {
all[acc] = data[acc]
return all
}, {})
console.log(res)

How to deep pick in another pick with lodash?

Now i have code like this:
var object = {
a: 'a',
b: 'b',
c: {
d: 'd'
}
}
_.get(object).pick(['a', 'b']).value();
How to deep pick property 'd' like:
_.get(object).pick(['a', 'b', 'c.d']).value();
you can deep destructure without lodash :
var object = {
a: 'a',
b: 'b',
c: {
d: 'd'
}
}
const { a, b, c :{ d }} = object;
console.log(a,b,d);
const obj = {a, b, d};
console.log(obj);
In case you insist in using Lodash, consider using the _.get() function:
_.get(object, 'c.d');
So, for the properties you want to get:
const selectedProps = {
..._.pick(object, ['a', 'b']),
_.get(object, 'c.d')
}
You can create a flatPick() function. The function iterates the array of paths. and uses _.get() to get the value of the path, and _.set() to add the last part of the path as property on the result object:
function flatPick(object, paths) {
const o = {};
paths.forEach(path => _.set(
o,
_.last(path.split('.')),
_.get(object, path)
));
return o;
}
var object = {
a: 'a',
b: 'b',
c: {
d: 'd',
e: {
f: 'f'
}
}
};
var result = flatPick(object, ['a', 'b', 'c.d', 'c.e.f']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
This can work for lodash:
function pickNested(object: Json, fields: string[]) {
const shallowFields = fields.filter((field) => !field.includes('.'));
const deepFields = fields.filter((field) => field.includes('.'));
const initialValue = _.pick(object, shallowFields) as Json;
return deepFields.reduce((output, field) => {
const key = _.snakeCase(field);
output[key] = _.get(object, field);
return output;
}, initialValue);
}
and:
const json = {
id: '10',
user: {
email: 'david.i#example.com',
},
};
const newData = pickNested(json, ['id', 'user.email']);
console.log('newData ->', newData);
/*
{
id: '10',
user_email: 'david.i#example.com',
};
*/

Categories

Resources