How to convert an array of objects back to one object - javascript

I got an array of objects that has the following structure:
const cars = {
ford: {
entries: ['ford 150']
},
chrysler: {
entries: ['Voyager']
},
honda: {
entries: []
},
toyota: {
entries: ['sienna']
},
mercedes: {
entries: []
},
}
For the user to be able to rearrange the order of the cars, I need to filter out the car brands that have zero entries, so I do this:
const filteredCars = Object.values(cars).filter(removeCarsWithNoEntries)
function removeCarsWithNoEntries(e) {
if (e.entries[0]) return e.entries
}
Then, once the order is rearranged, I have to convert the object back to its original form, but with the order rearranged, meaning something like this:
cars = {
toyota: {
entries: ['sienna']
},
chrysler: {
entries: ['Voyager']
},
ford: {
entries: ['ford 150']
},
honda: {
entries: []
},
mercedes: {
entries: []
},
}
I've read upon array.reduce and Object.assign but so far the things I have tried do not work.
How can I convert the array of objects back to one single object with the original car brands that had 0 entries? Thanks.

You can convert the object to entries via Object.entries(), and then filter/sort by the value (the 2nd item in the pair), and convert back to an object using Object.fromEntries():
const cars = {"ford":{"entries":["ford 150"]},"chrysler":{"entries":["Voyager"]},"honda":{"entries":[]},"toyota":{"entries":["sienna"]},"mercedes":{"entries":[]}}
const sorted = Object.fromEntries(
Object.entries(cars)
.sort(([, { entries: a }], [, { entries: b }]) => b.length - a.length)
)
const filtered = Object.fromEntries(
Object.entries(cars)
.filter(([, v]) => v.entries.length)
)
console.log({ sorted, filtered })

Checking your code... the filter method doesn't works because return an array...
do you need use length property to check if has values, otherwise it will always be true
const cars = {
ford: {
entries: ['ford 150']
},
chrysler: {
entries: ['Voyager']
},
honda: {
entries: []
},
toyota: {
entries: ['sienna']
},
mercedes: {
entries: []
},
}
filtered = {}
Object.keys(cars).forEach(removeCarsWithNoEntries)
function removeCarsWithNoEntries(brand) {
if(!cars[brand].entries.length) return
filtered[brand] = cars[brand]
}
console.log(filtered)

Related

sort objects by key value 0 by number size

how i can sort this object by user_id ?
{
key_1: { user_id: 3 },
key_2: { user_id: 1 },
key_3: { user_id: 2 }
}
I need this:
{
key_2: { user_id: 1 },
key_3: { user_id: 2 },
key_1: { user_id: 3 }
}
thanks for help
ES6 states a traversal order for object keys (see this article):
Integer indices in ascending numeric order.
Then, all other string keys, in the order in which they were added to the object.
Lastly, all symbol keys, in the order in which they were added to the object.
This means that as long as you're using non integer keys of strings or symbols (not both), you can "sort" an object keys by creating a new object with the keys in the insertion order you need.
For example, use Object.entries() to get an array of [key, value] pairs, sort by user_id, and then convert back to an object using Object.fromEntries():
const obj = { key_1: { user_id: 3 }, key_2: { user_id: 1 }, key_3: { user_id: 2 }}
const result = Object.fromEntries(
Object.entries(obj)
.sort(([, a], [, b]) => a.user_id - b.user_id)
)
console.log(result)
However, this would fail for an object with integer keys:
const obj = { 1: { user_id: 3 }, 2: { user_id: 1 }, 3: { user_id: 2 }}
const result = Object.fromEntries(
Object.entries(obj)
.sort(([, a], [, b]) => a.user_id - b.user_id)
)
console.log(result)
So, it's better and less error prone to create an array of keys, sort it by the order you want, and then use it to get property values in that order:
const obj = { key_1: { user_id: 3 }, key_2: { user_id: 1 }, key_3: { user_id: 2 }}
const result = Object.keys(obj)
.sort((a, b) => obj[a].user_id - obj[b].user_id)
console.log(result.forEach(key => console.log(obj[key])))
You can't. I think you are missing the nature of objects and arrays. The order of properties in an object doesn't matter. Of course, in an array it may.
If, for example, you receive an object and you want to display that data sorted, I would recommend you transform that object into an array and then sort it:
const object = {
key_1: {
user_id: 3
},
key_2: {
user_id: 1
},
key_3: {
user_id: 2
}
};
const array = Object.entries(object)
const sortedArray = array.sort(([, aValue], [, bValue]) => aValue.user_id > bValue.user_id)
console.log(sortedArray)

Flatten an array of objects and get unique keys and values by a repeated date with Lodash?

I've been playing around with Lodash and not getting close to a solution that doesn't involve a lot of extra looping and overhead.
data: [
{
name: "FirstResult", values: [
{
value: { NameCount: 1, OtherCount: 1 },
date: 2019-05-15T07:00:00+0000
},
{
value: { NameCount: 1 },
date: 2019-05-16T07:00:00+0000
}
]
},
{
name: "SecondResult",
values: [
{
value: { NameCount: 1 },
date: 2019-05-15T07:00:00+0000
},
{
value: { BuyCount: 2, SellCount: 1 },
date: 2019-05-16T07:00:00+0000
}
]
}
]
I'd like to flatten this and have it combined and aggregated by using the date as the key returning some configuration like:
[
{ date: 2019-05-15T07:00:00+0000, values: { NameCount: 2, OtherCount: 1 } },
{ date: 2019-05-16T07:00:00+0000, values: { NameCount: 1, BuyCount: 2, SellCount: 1 } }
]
Or even just a flat object array is fine like:
[
{ date: 2019-05-15T07:00:00+0000, NameCount: 2, OtherCount: 1 },
{ date: 2019-05-16T07:00:00+0000, NameCount: 1, BuyCount: 2, SellCount: 1 }
]
Does anyone have any ideas on how to do this with either a Lodash or Vanilla solution?
You can use a lodash's chain to flatten, group by the date, and then map and merge each group to a single object:
const fn = data => _(data)
.flatMap('values') // flatten to array of objects
.groupBy(o => o.date.toISOString()) // group by the iso representation
.map(group => { // map the groups by merging, and converting to require format
const { date, value } = _.mergeWith({}, ...group, (objValue, srcValue) =>
_.isNumber(objValue) ? objValue + srcValue : undefined // combine numeric values
)
return {
date,
...value,
}
})
.value()
const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Or you can use _.flow() to generate the function (I'm using lodash/fp here):
const { flow, flatMap, groupBy, map, mergeAllWith, cond, isNumber, add } = _
const fn = flow(
flatMap('values'), // flatten to array of objects
groupBy(o => o.date.toISOString()), // group by the iso representation
map(mergeAllWith(cond([[isNumber, add]]))), // combine numeric values
map(({ date, value }) => ({ date, ...value })) // format the objects
)
const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]
const result = fn(data)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
Here is pure ES6 solution based on Array.reduce and Array.forEach for the object keys:
const data = [{"name":"FirstResult","values":[{"value":{"NameCount":1,"OtherCount":1},"date": new Date("2019-05-15T07:00:00.000Z")},{"value":{"NameCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]},{"name":"SecondResult","values":[{"value":{"NameCount":1},"date":new Date("2019-05-15T07:00:00.000Z")},{"value":{"BuyCount":2,"SellCount":1},"date": new Date("2019-05-16T07:00:00.000Z")}]}]
let result = data.reduce((r, { values }) => {
values.forEach(({ value, date }) => {
let keys = Object.keys(value), d = date.toISOString()
r[d] = r[d] || Object.assign({}, ...keys.map(x => ({ date: d, [x]: 0 })))
keys.forEach(k => r[d][k] = (r[d][k] || 0) + value[k])
})
return r
}, {})
console.log(Object.values(result))
The main idea is to get the keys of the value object white iterating and compose an object with them while grouping by the date. Then last forEach just sums each result object value.

Filter array of objects based on criteria in another array of objects

I want to filter items from the categories array based on the criteria in the otherCategories array.
If otherCategories contains an object where title matches one title from categories.subCategory[i].title and name matches categories.subCategory[i].details.name, then filter only that object e.g "item1" from categories.
var categories = [
{
title:"item1",
subCategory:[
{
title:"subCat1",
details:{
name:"detail1",
email:"test#test.com"
}
},
{
title:"subCat2",
details:{
name:"detail2",
email:"test#test.com"
}
}
]
},
{
title:"item2",
subCategory:[
{
title:"subCat1",
details:{
name:"detail3",
email:"test#test.com"
}
},
{
title:"subCat2",
details:{
name:"detail2",
email:"test#test.com"
}
}
]
}
]
var otherCategories = [
{
title:"subCat1",
name:"detail1"
}
]
Expected result
categories = [
{
title:"item1",
subCategory:[
{
title:"subCat1",
details:{
name:"detail1",
email:"test#test.com"
}
},
{
title:"subCat2",
details:{
name:"detail2",
email:"test#test.com"
}
}
]
}]
Use Array.reduce, Array.filter & Array.some
Convert the otherCategories array to an object with title as key and name as value
Filter categories array where some subCategory exists with matching values
var categories = [{title:"item1",subCategory:[{title:"subCat1",details:{name:"detail1",email:"test#test.com"}},{title:"subCat2",details:{name:"detail2",email:"test#test.com"}}]},{title:"item2",subCategory:[{title:"subCat1",details:{name:"detail3",email:"test#test.com"}},{title:"subCat2",details:{name:"detail2",email:"test#test.com"}}]}];
var otherCategories = [{title:"subCat1",name:"detail1"}];
var obj = otherCategories.reduce((a,c) => Object.assign(a,{[c.title]:c.name}), {});
categories = categories.filter(v => v.subCategory.some(o => obj[o.title] === o.details.name));
console.log(categories);
You could map the categories to the results by filtering the subCategories:
function matches(sub, filters) {
return filters.some(filter => filter.title === sub.title && filter.name === sub.name);
}
const result = categories.map(({ title, subCategories }) => ({ title, subCategories: subCategories.filter(sub => matches(sub, otherCategories)) }));
Another approach that will usually work for simple objects like the ones in your example, is to convert your otherCategories array to an array of "stringified" objects, and then filter categories by comparing "stringified" versions of the desired subCategory key value pairs to the converted otherCategories array.
Important to note, however, that object property order is not guaranteed in JavaScript (although many browsers will preserve property order). That means that this approach may not work in some situations and an approach like the one suggested by #NikhilAggarwal is more stable.
For example:
const categories = [{title: "item1", subCategory: [{title: "subCat1", details: {name: "detail1", email: "test#test.com"}},{title: "subCat2", details: {name: "detail2", email: "test#test.com"}}]}, {title: "item2", subCategory: [{title: "subCat1", details: {name: "detail3", email: "test#test.com"}},{title: "subCat2", details: {name: "detail2", email: "test#test.com"}}]}];
const otherCategories = [{title: "subCat1", name: "detail1"}];
const matches = otherCategories.map((item) => JSON.stringify(item));
const results = categories.filter((item) => {
for (const sub of item.subCategory) {
const match = JSON.stringify({title: sub.title, name: sub.details.name});
if (matches.includes(match)) {
return true;
}
return false;
}
});
console.log(results);

How to create/merge object from splitted string array in TypeScript?

I have an array of objects like below;
const arr1 = [
{"name": "System.Level" },
{"name": "System.Status" },
{"name": "System.Status:*" },
{"name": "System.Status:Rejected" },
{"name": "System.Status:Updated" }
]
I am trying to split name property and create an object. At the end I would like to create an object like;
{
"System.Level": true,
"System.Status": {
"*": true,
"Rejected": true,
"Updated": true
}
}
What I have done so far;
transform(element){
const transformed = element.split(/:/).reduce((previousValue, currentValue) => {
previousValue[currentValue] = true;
}, {});
console.log(transofrmed);
}
const transofrmed = arr1.foreEach(element => this.transform(element));
The output is;
{System.Level: true}
{System.Status: true}
{System.Status: true, *: true}
{System.Status: true, Rejected: true}
{System.Status: true, Updated: true}
It is close what I want to do but I should merge and give a key. How can I give first value as key in reduce method? Is it possible to merge objects have same key?
You could reduce the splitted keys adn check if the last level is reached, then assign true, otherwise take an existent object or a new one.
const
array = [{ name: "System.Level" }, { name: "System.Status" }, { name: "System.Status:*" }, { name: "System.Status:Rejected" }, { name: "System.Status:Updated" }],
object = array.reduce((r, { name }) => {
var path = name.split(':');
last = path.pop();
path.reduce((o, k) => o[k] = typeof o[k] === 'object' ? o[k] : {}, r)[last] = true;
return r;
}, {});
console.log(object);
Use Array.reduce() on the list of properties. After splitting the path by :, check if there is second part. If there is a second part assign an object. Use object spread on the previous values, because undefined or true values would be ignored, while object properties would be added. If there isn't a second part, assign true as value:
const array = [{ name: "System.Level" }, { name: "System.Status" }, { name: "System.Status:*" }, { name: "System.Status:Rejected" }, { name: "System.Status:Updated" }];
const createObject = (arr) =>
arr.reduce((r, { name }) => {
const [first, second] = name.split(':');
r[first] = second ? { ...r[first], [second]: true } : true;
return r;
}, {});
console.log(createObject(array));

How to update state of nested array of array of objects without mutating

I am tryin to update the state of nested array of objects, for instance add to results[0].rooms[2] -> loop rooms -> and add to each object room.sunday.flag
array of results:
results: [
{ dates:
{ weekstart: 2018-08-26T04:00:00.000Z,
weekend: 2018-09-02T03:59:59.000Z,
daysofweek: [Array] },
need: [ '103', '204', '12', '234', '34', '555', '44' ],
backorder:
[ '100', '102', '150', '403', '344', '12', '3434', '23', '44' ],
_id: 5b7b139465c29145d8d69ac2,
iscurrent: true,
isnext: false,
rooms: [ [Object], [Object], [Object], [Object] ],
user: 5b61b782719613486cdda7ec,
__v: 9 },
{ dates:
{ weekstart: 2018-09-02T04:00:00.000Z,
weekend: 2018-09-09T03:59:59.000Z,
daysofweek: [Array] },
need: [ '12', '13', '55', '45' ],
backorder: [ '10', '11', '112' ],
_id: 5b83fdc500b6b6dc493e9bb8,
iscurrent: false,
isnext: true, rooms: [ [Object], [Object], [Object],
[Object] ],
user: 5b61b782719613486cdda7ec,
__v: 9 }
]
my attempt to change the state without mutating
const resultsRooms = [
...this.state.results[0].rooms ];
const rooms = resultsRooms.map((room, roomIndex) =>
{
room.sunday.flag = true;
});
const resultsUpdaye = this.results.forEach((element, index) => {
if (index === 0) {
elsment.rooms = rooms;
}
});
this.setState({
results: resultsUpdaye
});
any help? what am i doin wrong
Your resultsRooms.map is wrong. First of all, it does not return an object. If you use an arrow function and want to return an object, you must enclose it with parentheses.
const foo = () => ( { foo: "bar" } );
If you don't do that the function sees {} as a body block.
Your second problem is, map returns an array, does not do operations on items. So you can't do this: room.sunday.flag = true;
Here is the working version:
const rooms = resultsRooms.map((room, roomIndex) =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
So, we map the rooms, then return an object with spread syntax. With ...room we keep the parts of the room other than the sunday object. With sunday: {...room["sunday"], flag: true } we keep the parts of the sunday other than the flag property. Also, actually we don't need to make a copy with:
const resultsRooms = [
...this.state.results[0].rooms ];
Since we don't mutate it, with map we are creating a new array. So, here is the last version:
const rooms = results[0].rooms.map((room, roomIndex) =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
This part is OK, we don't mutate the state but if you use forEach on results you mutate the original data. Don't do it.
Here is a concise and maybe a cleaner alternative without using forEach.
const newRooms = results[0].rooms.map( room =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
const newItem = { ...results[0], rooms: newRooms };
const newResults = Object.assign([], results, { 0: newItem } );
Update after comments
I used Object.assign here to replace the item without mutating the original array. When using React (also if you prefer to use with React, this applies to Redux also) we should avoid mutating our data, namely our state. So, when you plan to change something in the state you should do it in proper ways. Your question actually indicates that: "without mutating". This is why I used Object.assign here.
I could choose other methods like:
const newResults = results.slice();
newResults[ 0 ] = newItem;
or
const newResults = [ ...results ];
newResults[ 0 ] = newItem;
Those are ok just for replacing the item as a whole. What do I mean here? slice and spread syntax does not create deep copies, they just do shallow copies. If you change a property of an object in the newly created array, the original one also mutates. This also applies to objects when we change nested properties. Actually, original source is objects here.
Here is an example array:
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
and an example item to change for index 2 (last item here);
const newItem = { id: 100, name: "change" };
const newArr = [ ...arr ];
arr[ 2 ] = newItem;
This is ok since we change a whole object here. Let's see:
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
const newItem = { id: 100, name: "change" };
const newArr = [ ...arr ];
newArr[ 2 ] = newItem;
console.log("new array", newArr );
console.log( "original array", arr );
Original one does not change. But if we do this:
newArr[ 2 ].id = 100; // ie, we only want to change the id, right?
Let's see:
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
const newArr = [ ...arr ];
newArr[ 2 ].id = 100;
console.log("new array", newArr );
console.log( "original array", arr );
So, our original array also changed. Mutation! If you don't change any property like this you can choose one of all three alternatives. But if you change a property you should think something better.
const arr = [
{ id:1, name:"foo" },
{ id:2, name:"bar" },
{ id:3, name:"baz" },
];
const newArr = Object.assign( [], arr, { 2: { ...arr[ 2 ], id: 100 } } );
console.log("new array", newArr );
console.log( "original array", arr );
Duh! :) Maybe there are better ways of doing this :) In any case, be very careful about mutating the state.
Just with an object:
const obj = {
user: {
id: 1,
name: "foo",
}
};
const newObj = { ...obj }
newObj.user.id = 1000
console.log( "new object", newObj );
console.log( "original object", obj );
Ok, this is enough for an explanation :)
Thank you for the answer. I am a bit confused, am I supposed to set
the state as follows: this.setState({ results: newResults });
Yes.
also what if i want to change the state of all results array rooms
results[0],results[1],results[2] .....
Come on, you have a great tool here :) map
const newResults = results.map( item => {
const newRooms = item.rooms.map( room =>
( { ...room, sunday: {...room["sunday"], flag: true } } )
);
const newItem = { ...item, rooms: newRooms };
return newItem;
})
First, we map the results, for each item, we map rooms and change the data, return the new item. So in the end, we get a new array where all its items changed, but again without any mutation.
I suggest playing a little bit with map, filter and maybe reduce methods. Also looking for spread syntax or if you prefer Object.assign is a plus.

Categories

Resources