I have an array of Foo called fooArray, but I would like to map() the array to only contain the “key: value” pairs which are defined in arrayOfKeys.
class Foo {
id: number;
name: string;
age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
}
let fooArray: Foo[] = [
new Foo(1, 'Foo', 20),
new Foo(2, 'Bar', 21),
new Foo(3, 'MyFoo', 20)
];
//The keys I would like to select from Foo.
const arrayOfKeys: (keyof Foo)[] = ['name', 'age'];
I do not know what to do to get the desired result below:
// The result is a copy of 'fooArray', but the objects only
// contain the keys (and their values) defined in 'arrayOfKeys'.
[
{ name: 'Foo', age: 20 },
{ name: 'Bar', age: 21 },
{ name: 'MyFoo', age: 20 }
]
You can probably do this by creating a new object in the map and returning it?
Something like
const fooArrayWithLimitedKeys = fooArray.map(item => arrayOfKeys.reduce(
(accumulator, key) => {
accumulator[key] = item[key]
return accumulator
}, {})
)
it can also be written without reduce like follows:
const fooArrayWithLimitedKeys = fooArray.map(item => {
const returnValue = {}
arrayOfKeys.forEach(key => {
returnValue[key] = item[key]
})
return returnValue;
})
Considering you simply want to modify each item of your array of objects to only contain desired keys, you may go like that:
const src = [
{ id: 1, name: 'Foo', age: 20 },
{ id: 2, name: 'Bar', age: 21 },
{ id: 3, name: 'MyFoo', age: 20 }
]
const keys = ['name', 'age']
const result = src.map(item => Object.assign(
...keys.map(key => ({[key]: item[key]}))
))
console.log(result)
You can use Object Destructuring, inside a map function, something like that.
class Foo {
id: number;
name: string;
age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
}
let fooArray: Foo[] = [
new Foo(1, 'Foo', 20),
new Foo(2, 'Bar', 21),
new Foo(3, 'MyFoo', 20)
];
const arrayOfKeys: (keyof Foo)[] = ['name', 'age'];
const result = fooArray.map(element =>(({name,age}) => ({name,age}))(element));
console.log(result);
First of all: if you don't really need to reassign fooArray further in code I would recommend it be a const.
Then you can add something like pick function, if you don't want to use lodash or underscore:
function pick(object: any, keys: string[]) {
return keys.map((key: string) => ({ [key]: object[key] }))
}
And use it like this:
const res = fooArray.map(el => pick(el, arrayOfKeys))
Your can create generic function which will accept target array and array of fields to be extracted. You should loop on desired fields and save them to result object. Heres solution in TypeScript:
Generic function
function mapToLimitedProps<A, Keys extends (keyof A)[]>(arr: A[], keys: Keys) {
return arr.map(item => {
let result: Partial<{
[key in Keys[number]]: A[key]
}> = {};
keys.forEach(key => {
result[key] = item[key];
})
return result;
})
}
Usage
mapToLimitedProps(fooArray, arrayOfKeys);
Related
I have an object:
const obj = {
name: "foo",
other: "something"
}
Creating a new object based on my object (a shallow copy) I would use the spread operator and then changing name on my new object:
const newObj = {...obj}
newObj.name = "bar";
But recently I ran into syntax that also does the trick:
const newObj = {
...obj, name: "bar"
}
How does this work and what is it called?
The two methods, you specified are equivalent. In the second method
const newObj = {
...obj, name: "bar"
}
It creates anothey key value pair with key=name and value="bar", since 1 key cannot have multiple values (if its not an array). It overtires the previous value. Code in the second method is equivalent to
const newobj = {
name: "foo",
other: "something",
name: "bar"
}
This is how spread operator works, It allows allows an iterable to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected.
This explains what it does. I hope it helps.
In addition to creating shallow copies, spread allows you to merge objects. The latter will override the former. For example:
const obj = { name: 'jane', age: 22 }
const merged = { ...obj, age: 21 } // { name: 'jane', age: 21 }
const obj = { name: 'jane', age: 22 }
const merged = { ...obj, hasDog: true } // { name: 'jane', age: 22, hasDog: true }
const obj = { name: 'jane', age: 22 }
const obj2 = { age: 19, hasDog: true }
const merged = { ...obj, ...obj2 } // { name: 'jane', age: 19, hasDog: true }
MDN - Spread Syntax
I have the following source array:
const list = [
{
students: [ 'peter', 'bob', 'john']
},
{
students: [ 'thomas', 'sarah', 'john']
},
{
students: [ 'john', 'sarah', 'jack']
}
];
and i want to get the unique student names and their count, final result should be like:
{
'john': 3,
'sarah': 2,
'thomas': 1,
'jack': 1,
'peter': 1,
'bob': 1
}
here is my attempt:
const unique = list.reduce(function(total, curr){
const students = curr.students;
for (c of students) {
if (!total[c]) {
total[c] = 1
} else {
total[c] += 1;
}
}
return total;
}, {});
is there a better way to do it? or faster and cleaner way? thanks
I'd flatten the arrays first, then count up with reduce:
const list = [
{
students: [ 'peter', 'bob', 'john']
},
{
students: [ 'thomas', 'sarah', 'john']
},
{
students: [ 'john', 'sarah', 'jack']
}
];
const allStudents = list.flatMap(({ students }) => students);
const count = allStudents.reduce((a, name) => {
a[name] = (a[name] || 0) + 1;
return a;
}, {});
console.log(count);
If you want the properties to be ordered as well, then take the Object.entries of the object, sort it, then turn it back into an object with Object.fromEntries:
const list = [
{
students: [ 'peter', 'bob', 'john']
},
{
students: [ 'thomas', 'sarah', 'john']
},
{
students: [ 'john', 'sarah', 'jack']
}
];
const allStudents = list.flatMap(({ students }) => students);
const count = allStudents.reduce((a, name) => {
a[name] = (a[name] || 0) + 1;
return a;
}, {});
const sorted = Object.fromEntries(
Object.entries(count).sort((a, b) => b[1] - a[1])
);
console.log(sorted);
If your environment doesn't support flatMap, or fromEntries, use a polyfill, or flatten/group with a different method:
const list = [
{
students: [ 'peter', 'bob', 'john']
},
{
students: [ 'thomas', 'sarah', 'john']
},
{
students: [ 'john', 'sarah', 'jack']
}
];
const allStudents = [].concat(...list.map(({ students }) => students));
const count = allStudents.reduce((a, name) => {
a[name] = (a[name] || 0) + 1;
return a;
}, {});
const sortedEntries = Object.entries(count).sort((a, b) => b[1] - a[1]);
const sortedObj = sortedEntries.reduce((a, [prop, val]) => {
a[prop] = val;
return a;
}, {});
console.log(sortedObj);
Keep in mind that object property order is only specified in ES6+ environments. While Object.fromEntries isn't guaranteed by the specification to create an object in the same order as the entries, it does anyway, in any implementation I've ever encountered, luckily. (If you're still worried about it, you can use the old-fashioned reduce method to create the object instead, like in the third snippet)
Try using functional programming: combination of map and 2 reduce methods.
const listMapped = list.map(it=> it.students)
const listReduced = listMapped.reduce((acc, rec) => {
return [...acc.concat(rec)]
}, [])
const listCounted = listReduced.reduce((acc, rec) => {
acc[rec]
? acc[rec] += 1
: acc[rec] = 1
return acc
}, {})
console.log(listCounted)
Suppose I got this array:
const users =[
{
id:1,
name:'bob',
},
{
id:2,
name:'sally',
},
{
id:3,
name:'bob',
age:30,
}
];
And I want to use any key(in this case 'name' ) to return an object :
{
bob:[
{
id:1,
name:'bob',
},
{
id:3,
name:'bob',
age:30,
}
],
sally:[
{
id:2,
name:'sally',
}
],
}
I tried this:
const go = (A,key) =>{
return A.reduce((o, key) => ({ ...o, [key]:o }), {})
}
export default go;
But this returns:
{ '[object Object]': { '[object Object]': { '[object Object]': {} } } }
If the key is not present omit from the result. It should not mutate the original array though. How can I perform this kind of conversion?
With the approach you have, a new array is not instantiated in case the key is not yet present in the object.
This will work:
const result = users.reduce((a, v) => {
a[v.name] = a[v.name] || [];
a[v.name].push(v);
return a;
}, {});
Complete snippet wrapping this logic in a function:
const users = [{
id: 1,
name: 'bob',
}, {
id: 2,
name: 'sally',
}, {
id: 3,
name: 'bob',
age: 30,
}];
const go = (input, key) => input.reduce((a, v) => {
a[v[key]] = a[v[key]] || [];
a[v[key]].push(v);
return a;
}, {});
console.log(go(users, 'name'));
If you really want to cram it into a one-liner, this will also work, by either spreading the already existing array, or an empty one:
const result = users.reduce((a, v) => ({...a, [v.name]: [...a[v.name] || [], v]}), {});
Complete snippet wrapping this logic in a function:
const users = [{
id: 1,
name: 'bob',
}, {
id: 2,
name: 'sally',
}, {
id: 3,
name: 'bob',
age: 30,
}];
const go = (input, key) => input.reduce((a, v) => ({...a, [v[key]]: [...a[v[key]] || [], v]}), {});
console.log(go(users, 'name'));
You were close but the key attribute in this case was each value (eg: { id: 1, name: 'bob' }) so the string representation is [object Object] which is why all the keys are that. Based off what you said, you want to use key.name as the property and set it's value as [key]. (I renamed key to arr in my example since it's the array value).
So this would be something like { ...o, [arr.name]: [arr] }
Because there can be an existing value, it adds a bit of complexity which is what [...(obj[arr.name] || []), arr] is doing. It's looking up the existing value (or defaulting to an empty array) and spreading those values and adding the new value.
const users = [{
id: 1,
name: 'bob',
},
{
id: 2,
name: 'sally',
},
{
id: 3,
name: 'bob',
age: 30,
}
];
const transform = (input, keyName) => {
return input.reduce((obj, arr) => ({ ...obj,
[arr[keyName]]: [...(obj[arr[keyName]] || []), arr]
}), {})
}
console.log(transform(users, 'name'))
console.log(transform(users, 'id'))
Let say I have json like this (use JSON.stringify)
{ name: 'Bill', lastname: 'Smith'}
And I want the value wrapped with curly braces like this
{ name: { value: 'Bill' }, lastname: { value: 'Smith'} }
So any idea to do like this using javascript or lodash?
I'd use Object.entries on the input, map to a nested object, then call Object.fromEntries to transform it back again:
const input = { name: 'Bill', lastname: 'Smith'};
const newObj = Object.fromEntries(
Object.entries(input).map(
([key, value]) => ([key, { value }])
)
);
console.log(newObj);
Object.fromEntries is a pretty new method, so for older browsers, either include a polyfill or use something like .reduce instead:
const input = { name: 'Bill', lastname: 'Smith'};
const newObj = Object.entries(input).reduce(
(a, [key, value]) => {
a[key] = { value };
return a;
},
{}
);
console.log(newObj);
You can loop through the keys of the object using for...in and update it like this:
const input = { name: 'Bill', lastname: 'Smith'};
for (const key in input) {
input[key] = { value: input[key] }
}
console.log(input)
If you don't want to mutate the input and want to create a new object, then create another object and update it:
const input = { name: 'Bill', lastname: 'Smith'},
output = {}
for (const key in input) {
output[key] = { value: input[key] }
}
console.log(output)
You can use lodash's _.mapValues() to return a new object with transformed values:
const object = { name: 'Bill', lastname: 'Smith'};
const result = _.mapValues(object, value => ({ value }));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
I have a similar object as this one but it has quite more keys. I want to update all of its keys but id. I can do so manually. But I think it is not the best way.
const o = {
name: "unknow",
key: "key"
value: "value"
id ": 12
}
How can I update/override all keys of an object but id?
Update
The two object has the same keys. But their keys have different value. I need to update all keys of the first object excluding its id.
I suspect that you're looking for something like assignBut: it sets properties of ob on oa but the specified one:
const assignBut = (prop, oa, ob) => {
for (let key of Object.keys(ob))
// Check that I also verify that the property
// to set should be part of "oa" object. This
// prevents adding new properties: it just updates
// existing ones.
if (key !== prop && oa.hasOwnProperty(key))
oa[key] = ob[key]
}
const oa = {
name: "unknow",
key: "key",
value: "value",
id: 12
}
const ob = {
name: "xxx",
key: "yyy",
value: "zzz",
other: "aaa",
yetAnother: 289,
id: 15
}
assignBut('id', oa, ob)
console.log(oa)
Another approach to omit a given property
One may take advantage of destructuring and computed property names to omit the whole given property so the for..of just needs to check that each property from ob is present in oa to set it.
Also, one may save the check to verify that a property from ob exists in oa performing an intersection of oa and ob keys:
const oa = {
name: "unknow",
key: "key",
value: "value",
id: 12
}
const ob = {
name: "xxx",
key: "yyy",
value: "zzz",
other: "aaa",
yetAnother: 289,
id: 15
}
const intersect = (xs, ys) => xs.filter(x => ys.includes(x))
const assignBut = (prop, oa, {
[prop]: omitted,
...ob
}) => {
const sharedKeys = intersect(Object.keys(oa), Object.keys(ob))
for (let key of sharedKeys)
oa[key] = ob[key]
}
assignBut('id', oa, ob)
console.log(oa)
You can iterate through Object.keys like below -
const o = {
name: "unknow",
key: "key",
value: "value",
id : 12
};
Object.keys(o).forEach((key)=>{
if(key !=="id"){
console.log(o[key]) //value
}
}
);
Following approach is based on lodash. If you are not comfortable using a library, please ignore.
Benefit of omit is that you can pass an array of keys and ignore multiple keys.
There is also a function called pick where you can only pick certain properties you need.
_.omit
const o = { name: "unknow", key: "key", value: "value", id: 12 }
const props = { name: "foo", key: "key2", value: "bar", id: 15 };
const final = _.assign({}, o, _.omit(props, 'id'));
console.log(final)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
_.pick
const o = { name: "unknow", key: "key", value: "value", id: 12 }
const props = { name: "foo", key: "key2", value: "bar", id: 15, test: 'abc', hello: 'world' };
const final = _.assign({}, o, _.pick(props, ['name', 'key', 'value']));
console.log(final)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
pure js implementation
const o = { name: "unknow", key: "key", value: "value", id: 12 }
const propsOmit = { name: "foo", key: "key2", value: "bar", id: 15 };
const propsPick = { name: "foo", key: "key2", value: "bar", id: 15, test: 'abc', hello: 'world' };
const finalOmit = Object.assign({}, o, omit(propsOmit, 'id'));
const finalPick = Object.assign({}, o, omit(propsPick, ['id', 'test', 'hello']));
console.log(finalOmit)
console.log(finalPick)
function omit(obj, ignoreKeys) {
if (!Array.isArray(ignoreKeys)) {
ignoreKeys = [ ignoreKeys ];
}
const copy = Object.assign({}, obj);
ignoreKeys.forEach((k) => delete copy[k]);
return copy;
}
function pick(obj, selectKeys) {
if (!Array.isArray(selectKeys)) {
selectKeys = [ selectKeys ];
}
const copy = {};
ignoreKeys.forEach((k) => copy[k] = obj[k]);
return copy;
}
References:
_.assign
_.omit
_.pick