Modify and Combine Array of Objects by ID - javascript

I have the following array of objects:
[
{
"id": 1,
"price": 22,
"from": "00:00:00",
"to": "02:00:00"
},
{
"id": 1,
"price": 23,
"from": "02:00:00",
"to": "04:00:00"
},
{
"id": 2,
"price": 10,
"from": "04:00:00",
"to": "1.00:00:00"
}
]
I need to restructure it, so it combines the objects by ID, and creates a new nested array of objects:
[
{
"id": 1,
"prices": [
{
"price": 22,
"from": "00:00:00",
"to": "02:00:00"
},
{
"price": 23,
"from": "02:00:00",
"to": "04:00:00"
},
]
}
{
"id": 2,
"prices": [
{
"price": 10,
"from": "04:00:00",
"to": "1.00:00:00"
}
]
}
]
Is there a simple way to do this? I'm getting lost in split, forEach and maps. Thanks.

One way is to use a Map to collect the prices per id. First associate an empty prices list for each id, then iterate the data to populate those arrays, and finally extract the Map values to get the result:
const data = [{"id": 1,"price": 22,"from": "00:00:00","to": "02:00:00"},{"id": 1,"price": 23,"from": "02:00:00","to": "04:00:00"},{"id": 2,"price": 10,"from": "04:00:00","to": "1.00:00:00"}];
const map = new Map(data.map(({id}) => [id, { id, prices: [] }]));
for (const {id, ...rest} of data) map.get(id).prices.push(rest);
const result = [...map.values()];
console.log(result);

Grouping by hash can save you from calling .find() or .findIndex()
const data=[{id:1,price:22,from:"00:00:00",to:"02:00:00"},{id:1,price:23,from:"02:00:00",to:"04:00:00"},{id:2,price:10,from:"04:00:00",to:"1.00:00:00"}];
const result = Object.values(data.reduce((acc, { id, ...rest }) => {
acc[id] ??= { id, prices: [] };
acc[id].prices.push(rest);
return acc;
}, {}));
console.log(result);
.as-console-wrapper {max-height: 100% !important; top: 0}

You can use the Array.reduce method with Array.findIndex to convert your data.
const data = [{
"id": 1,
"price": 22,
"from": "00:00:00",
"to": "02:00:00"
},
{
"id": 1,
"price": 23,
"from": "02:00:00",
"to": "04:00:00"
},
{
"id": 2,
"price": 10,
"from": "04:00:00",
"to": "1.00:00:00"
}
];
const transformedData = data.reduce((acc, item) => {
const priceData = {
price: item.price,
to: item.to,
from: item.from,
};
const index = acc.findIndex(({
id
}) => id === item.id);
if (index === -1) {
return [
...acc,
{
id: item.id,
prices: [
priceData
]
},
];
} else {
acc[index].prices.push(priceData);
return acc;
}
}, []);
console.log(transformedData);

const items = [
{
"id": 1,
"price": 22,
"from": "00:00:00",
"to": "02:00:00"
},
{
"id": 1,
"price": 23,
"from": "02:00:00",
"to": "04:00:00"
},
{
"id": 2,
"price": 10,
"from": "04:00:00",
"to": "1.00:00:00"
}
]
const result = items
.map(i => i.id)
.filter((item, pos, self) => self.indexOf(item) == pos)
.map( i => ({
id : i,
prices : items
.filter(item => item.id === i)
.map(({ price, from, to}) => ({price, from , to}) )
}) )
console.log(result)

Yes, there is an easy way using reduce().
const input = [
{
id: 1,
price: 22,
from: "00:00:00",
to: "02:00:00",
},
{
id: 1,
price: 23,
from: "02:00:00",
to: "04:00:00",
},
{
id: 2,
price: 10,
from: "04:00:00",
to: "1.00:00:00",
},
];
const output = input.reduce((nested, cur) => {
const objWithoutId = (({id, ...o}) => o)(cur);
if (!nested[cur.id]) {
nested[cur.id] = {
id: cur.id,
prices: [objWithoutId]
};
}
else nested[cur.id].prices.push(objWithoutId);
return nested;
}, {});
console.log(Object.values(output));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Explanation
We loop over the input using a JavaScript object. For every object we check it's ID. When we don't have that ID stored in our object already we create a new object containing the id and a prices array as well as the current value (without the id property). If we have encountered the same id already we already just need to push the current value (without the id property) to the already existing array.
Since we only have one loop and the lookups take O(1) time this algorithm takes O(n) to give a valid result.
Using this one-liner
const objWithoutId = (({id, ...o}) => o)(cur);
we create a new object which contains all properties except id.
Lat but not least we just need to get the values of the created JavaScript object using Object.values(). Instead of a JavaScript object you could also use a Map to perform the equivalent algorithm with the same runtime properties.

You can use the Object.entries, Array#reduce and Array#map methods as follows:
const input = [ { "id": 1, "price": 22, "from": "00:00:00", "to": "02:00:00" }, { "id": 1, "price": 23, "from": "02:00:00", "to": "04:00:00" }, { "id": 2, "price": 10, "from": "04:00:00", "to": "1.00:00:00" } ],
output = Object.entries(input.reduce(
(acc, {id,...rest}) =>
({...acc, [id]: [...(acc[id] || []), rest]}), {}
))
.map(([id, prices]) => ({id,prices}));
console.log( output );

Related

Transform complex object into key value

I have an array
const arr = [
{
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"slug": "bitcoin",
"rank": 1,
"is_active": 1,
"first_historical_data": "2013-04-28T18:47:21.000Z",
"last_historical_data": "2022-02-18T11:39:00.000Z",
"platform": null
},
{
"id": 2,
"name": "Litecoin",
"symbol": "LTC",
"slug": "litecoin",
"rank": 20,
"is_active": 1,
"first_historical_data": "2013-04-28T18:47:22.000Z",
"last_historical_data": "2022-02-18T11:39:00.000Z",
"platform": null
}
]
And I want to transform the array to this
{
"BTC": "Bitcoin",
"LTC": "Litecoin",
}
Is there a better way than this?
const result = {}
arr.reduce((accum, val) => {
Object.assign(result, { [val.symbol]: val.name });
}, {})
console.log(result)
Use Object.entries() an each object which will return them as an array of arrays -- each sub-array will be a key/value pair ([key, value]) then use Object.assign() to create a new object ({[key]: value}) to return. Then flatten it so they are all in one array.
const arr=[{id:1,name:"Bitcoin",symbol:"BTC",slug:"bitcoin",rank:1,is_active:1,first_historical_data:"2013-04-28T18:47:21.000Z",last_historical_data:"2022-02-18T11:39:00.000Z",platform:null},{id:2,name:"Litecoin",symbol:"LTC",slug:"litecoin",rank:20,is_active:1,first_historical_data:"2013-04-28T18:47:22.000Z",last_historical_data:"2022-02-18T11:39:00.000Z",platform:null}];
const conv = array => {
let objects = array.map(obj => Object.entries(obj).map(([key, val]) => Object.assign({}, {[key]: val})));
return objects.flat();
};
console.log(conv(arr));
let arr = [
{
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"slug": "bitcoin",
"rank": 1,
"is_active": 1,
"first_historical_data": "2013-04-28T18:47:21.000Z",
"last_historical_data": "2022-02-18T11:39:00.000Z",
"platform": null
},
{
"id": 2,
"name": "Litecoin",
"symbol": "LTC",
"slug": "litecoin",
"rank": 20,
"is_active": 1,
"first_historical_data": "2013-04-28T18:47:22.000Z",
"last_historical_data": "2022-02-18T11:39:00.000Z",
"platform": null
}
]
// As suggested, here it is without having to create an initial array
let alternativeArray = arr.map((val) => {
return {[val.symbol]: val.slug}
})
console.log(alternativeArray)
Here is the answer to why the object key is being set in that format:
Javascript set object key by variable
The answer is
Object.fromEntries(arr.map(({symbol, name}) => [symbol, name]))

How to sort object according to nested array elements

I need to sort the main data by the oldest created account.
In the example below, the first element in the list would be id = 2. That's because the id = 2 contains the oldest created account (named account3, which was created at 2020-10-05, while the other accounts have been created after that date).
I'm using nodejs. Is there an es-function that can solve this problem in an easy way?
The object data looks like this:
{
"data": [{
"id": 1,
"accounts": [{
"id": 333,
"data": {
"name": "account1",
"createdAt": "2020-10-07T09:27:28.032Z"
}
}]
}, {
"id": 2,
"accounts": [{
"id": 334,
"data": {
"name": "account2",
"createdAt": "2020-10-06T09:27:28.032Z"
}
}, {
"id": 335,
"data": {
"name": "account3",
"createdAt": "2020-10-05T09:27:28.032Z"
}
}]
}]
}
You can often solve this problem with map --> sort --> map.
It does 3 passes on the input but remains O(n log n). You could further optimize, but I doubt this becomes a bottleneck.
Map to [record, oldestAccountDate] tuples.
Sort the tuples by oldestAccountDate.
Map again to unwrap the record.
const wrapper = {
"data": [{
"id": 1,
"accounts": [{
"id": 333,
"data": {
"name": "account1",
"createdAt": "2020-10-07T09:27:28.032Z"
}
}]
}, {
"id": 2,
"accounts": [{
"id": 334,
"data": {
"name": "account2",
"createdAt": "2020-10-06T09:27:28.032Z"
}
}, {
"id": 335,
"data": {
"name": "account3",
"createdAt": "2020-10-05T09:27:28.032Z"
}
}]
}]
};
wrapper.data = wrapper.data
.map(rec => [rec, Math.min(...rec.accounts.map(acc => new Date(acc.data.createdAt)))])
.sort((a, b) => a[1] - b[1])
.map(tuple => tuple[0]);
console.log(wrapper);
const data = [{
"id": 1,
"accounts": [{
"id": 333,
"data": {
"name": "account1",
"createdAt": "2020-10-07T09:27:28.032Z"
}
}]
}, {
"id": 2,
"accounts": [{
"id": 334,
"data": {
"name": "account2",
"createdAt": "2020-10-06T09:27:28.032Z"
}
}, {
"id": 335,
"data": {
"name": "account3",
"createdAt": "2020-10-05T09:27:28.032Z"
}
}]
}]
const sorted = data.sort((a, b) => {
const aOldestDate = a.accounts.reduce((acc, item) => {
const itemDate = new Date(item.data.createdAt);
return itemDate < acc && itemDate || acc;
}, new Date());
const bOldestDate = b.accounts.reduce((acc, item) => {
const itemDate = new Date(item.data.createdAt);
return itemDate < acc && itemDate || acc;
}, new Date());
return aOldestDate - bOldestDate;
});
console.log(sorted);

Remove objects from nested array using Lodash

I have the following JSON structure:
{
"id": 123,
"shops": [
{
"shopId": 456,
"products": [
{
"productId": 10001,
"name": "abc",
"state": "active"
},
{
"productId": 10002,
"name": "def",
"state": "expired"
}
]
},
{
"shopId": 789,
"products": [
{
"productId": 20001,
"name": "qrt",
"state": "expired"
},
{
"productId": 20002,
"name": "jbf",
"state": "active"
}
]
}
]
}
I want to remove all products from each shop where the product does not have certain properties.
If I covert it to a flat map then I can do it fine, but then I lose the outer object as I just have an array with all the products that haven't been removed in.
_(shopJson.shops).map('products').flatten().map(x => {if(x.state === 'active'){return x}}).compact().value()
I tried the following but just end up with an empty array:
_(shopJson.shops).map('products').filter(x => x.state === 'active').value()
I also tried using _.reduce() and _.transform() but can't get it to work
The final JSON should look like:
{
"id": 123,
"shops": [
{
"shopId": 456,
"products": [
{
"productId": 10001,
"name": "abc",
"state": "active"
}
]
},
{
"shopId": 789,
"products": [
{
"productId": 20002,
"name": "jbf",
"state": "active"
}
]
}
]
}
You don't really need lodash for this. You can just use Array.prototype.map and Array.protype.filter (and also some spread syntax to shallow merge object properties):
const data = {id:123,shops:[{shopId:456,products:[{productId:10001,name:"abc",state:"active"},{productId:10002,name:"def",state:"expired"}]},{shopId:789,products:[{productId:20001,name:"qrt",state:"expired"},{productId:20002,name:"jbf",state:"active"}]}]};
const result = {
...data,
shops: data.shops.map((shop) => ({
...shop,
products: shop.products.filter((product) => product.state === 'active'),
})),
};
console.log(result);
EDIT: As #Deykun pointed out, if you want to ignore shops that don't have any active products, you can filter shops out using Array.prototype.some in a filter:
const data = {id:123,shops:[{shopId:456,products:[{productId:10001,name:"abc",state:"active"},{productId:10002,name:"def",state:"expired"}]},{shopId:789,products:[{productId:20001,name:"qrt",state:"expired"},{productId:20002,name:"jbf",state:"expired"}]}]};
const result = {
...data,
shops: data.shops
.filter((shop) => shop.products.some((product) => product.state === 'active'))
.map((shop) => ({
...shop,
products: shop.products.filter((product) => product.state === 'active')
}))
};
console.log(result);

Filter an array inside an object inside an array in javascript

I have this object and I need a function to filter the pokemon (whole objects) by type or weaknesses (the user decides).
For example: "the user needs to filter all the fire type pokemon" and the result would be an array containing every fire type pokemon object
var POKEMON = {
"pokemon": [{
"id": 1,
"num": "001",
"name": "Bulbasaur",
"img": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png",
"type": [
"Grass",
"Poison"
],
"height": "0.71 m",
"weight": "6.9 kg",
"candy": "Bulbasaur Candy",
"candy_count": 25,
"egg": "2 km",
"spawn_chance": 0.69,
"avg_spawns": 69,
"spawn_time": "20:00",
"multipliers": [1.58],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"next_evolution": [{
"num": "002",
"name": "Ivysaur"
}, {
"num": "003",
"name": "Venusaur"
}]
},{
"id": 2,
"num": "002",
"name": "Ivysaur",
"img": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png",
"type": [
"Grass",
"Poison"
],
"height": "0.99 m",
"weight": "13.0 kg",
"candy": "Bulbasaur Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.042,
"avg_spawns": 4.2,
"spawn_time": "07:00",
"multipliers": [
1.2,
1.6
],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "001",
"name": "Bulbasaur"
}],
"next_evolution": [{
"num": "003",
"name": "Venusaur"
}]
},
... (+149 other pokemon)
I already have a function and it works but I'd rather not use for loops:
const filterPokemon = (data, whatToFilter, valueToCompare) => {
return data.filter(pokemon => {
for(let i = 0 ; i < pokemon[whatToFilter].length ; i++){
if(pokemon[whatToFilter][i] === valueToCompare){
return pokemon;
}
}
});
};
filterPokemon(POKEMON.pokemon, "type", "Fire");
Use .includes instead to see if any items in the pokemon[whatToFilter] array equal the valueToCompare:
const filterPokemon = (data, whatToFilter, valueToCompare) => {
return data.filter(creatureObj => (
creatureObj[whatToFilter].includes(valueToCompare)
));
};
Side note, might be a bit opinion-based, but you might consider changing the variable name from pokemon (eg here I used "creatureObj") because "pokemon" can be either singular or plural, so it isn't entirely clear what sort of object it is.
Could also avoid that entirely by destructuring the parameter, if you wanted:
const filterPokemon = (data, whatToFilter, valueToCompare) => {
return data.filter(({ [whatToFilter]: arr }) => (
arr.includes(valueToCompare)
));
};
You have the first part right, but just use the array find() operator to filter, or since these are simple primitive arrays, you could just use .indexOf(value) > -1 or .includes() or a few other operators. I prefer find because it works on complex types as well.
const filterPokemon = (pokemonList, propToFilter, value) => {
return pokemonList.filter(pokemon => {
return pokemon[propToFilter].find(p => p === value);
});
};
Another option is to use Array.some() inside the filter function.
const filterPokemon = (data, whatToFilter, valueToCompare) =>
{
return data.filter(pokemon => pokemon[whatToFilter].some(x => x === valueToCompare));
}
Example:
var POKEMON = {
"pokemon": [{
"id": 1,
"num": "001",
"name": "Bulbasaur",
"img": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/001.png",
"type": [
"Grass",
"Poison"
]
},
{
"id": 2,
"num": "002",
"name": "Ivysaur",
"img": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/002.png",
"type": [
"Grass",
"Poison",
"Fire"
],
}
]};
const filterPokemon = (data, whatToFilter, valueToCompare) =>
{
return data.filter(pokemon => pokemon[whatToFilter].some(x => x === valueToCompare));
}
let res = filterPokemon(POKEMON.pokemon, "type", "Fire");
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

Filter and clone object properties

I have 2 arrays of objects: itemsList and itemsFetched. All of the objects inside each array have the same structure (nr of key/values). One of those keys has the same 'meaning' but a different name (item_id on itemsList, id on itemsFetched ). Their values are the same.
I need to filter the itemsList array and leave only the objects that have the item_id value equal to the id value on itemsFetched. Then copy(add) the key/value count from each object on the itemsFetched array (which matches the item_id=id) to the filtered array.
I've a working code but I'm sure it isnt the best way to solve this problem. I've already asked something similar before (regarding the 'filter' part) which solved my problem, but since I had to add the 'count' part after the filtering, I ended up refactoring the whole thing.
itemsList (sample)
[
{
"id": 0,
"name": "Egg",
"img": "http://www.serebii.net/pokemongo/items/egg.png"
},
{
"id": 1,
"name": "Pokeball",
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png"
},
{
"id": 2,
"name": "Greatball",
"img": "http://www.serebii.net/pokemongo/items/greatball.png"
},
{
"id": 401,
"name": "Incense",
"img": "http://www.serebii.net/pokemongo/items/incense.png"
},
{
"id": 901,
"name": "Incubator (Unlimited)",
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}
]
itemsFetched (sample)
[
{
"item_id": 1,
"count": 50,
"unseen": true
},
{
"item_id": 401,
"count": 2,
"unseen": true
},
{
"item_id": 901,
"count": 1,
"unseen": true
}
]
resultArray (what I want in the end)
[
{
"id": 1,
"name": "Pokeball",
"count": 50,
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png",
},
{
"id": 401,
"name": "Incense",
"count": 2,
"img": "http://www.serebii.net/pokemongo/items/incense.png"
},
{
"id": 901,
"name": "Incubator (Unlimited)",
"count": 1,
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}
]
my current code (working)
let arr = [];
itemsFetched.forEach((item) => {
itemsList.forEach((item2) => {
if (item.item_id === item2.id) {
arr.push({
"id": item.item_id,
"name": item2.name,
"count": item.count,
"img": item2.img
});
}
});
});
PS: I'm able to use ES6/7 syntax/features.
You can use hash map to reduce Time complexitly, your algorithm is O(m*n), The follow is O(m+n+r)
const itemsMap = itemsList.reduce((map, item) => {
map[item.id] = item
return map
}, {})
const results = itemsFetched
.filter((item) => itemsMap.hasOwnProperty(item.item_id))
.map((item) => ({
id: item.item_id,
name: itemsMap[item.item_id].name,
count: item.count,
img: itemsMap[item.item_id].img,
}))
Use a for ... of loop (an ES6 feature) in conjunction with Array#map.
This makes it much easier to return the merged object the first time you find a match, which is a logically optimization because neither list should contain more than one entry with a given id.
const result = itemsFetched.map(data => {
for (let item of itemsList) {
if (data.item_id === item.id) {
return {
id: item.id,
name: item.name,
count: data.count,
img: item.img
}
}
}
})
Snippet:
const itemsList = [{
"id": 0,
"name": "Egg",
"img": "http://www.serebii.net/pokemongo/items/egg.png"
}, {
"id": 1,
"name": "Pokeball",
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png"
}, {
"id": 2,
"name": "Greatball",
"img": "http://www.serebii.net/pokemongo/items/greatball.png"
}, {
"id": 401,
"name": "Incense",
"img": "http://www.serebii.net/pokemongo/items/incense.png"
}, {
"id": 901,
"name": "Incubator (Unlimited)",
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}]
const itemsFetched = [{
"item_id": 1,
"count": 50,
"unseen": true
}, {
"item_id": 401,
"count": 2,
"unseen": true
}, {
"item_id": 901,
"count": 1,
"unseen": true
}]
const result = itemsFetched.map(data => {
for (let item of itemsList) {
if (data.item_id === item.id) {
return {
id: item.id,
name: item.name,
count: data.count,
img: item.img
}
}
}
})
console.log(result)
One way to improve is to use for..of statement instead of forEach for the inner loop. This helps break from the loop once the id matches. There is no direct way to break from forEach method.
let arr = [];
itemsFetched.forEach((item) => {
for (let item2 of itemsList) {
if (itemsFetched.item_id === itemsList.id) {
arr.push({
"id": itemsFetched.item_id,
"name": itemsList.name,
"count": itemsFetched.count,
"img": itemsList.img
});
break;
}
}
});
Like this?
var itemsList = [
{
"id": 0,
"name": "Egg",
"img": "http://www.serebii.net/pokemongo/items/egg.png"
},
{
"id": 1,
"name": "Pokeball",
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png"
},
{
"id": 2,
"name": "Greatball",
"img": "http://www.serebii.net/pokemongo/items/greatball.png"
},
{
"id": 401,
"name": "Incense",
"img": "http://www.serebii.net/pokemongo/items/incense.png"
},
{
"id": 901,
"name": "Incubator (Unlimited)",
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}
];
var itemsFetched = [
{
"item_id": 1,
"count": 50,
"unseen": true
},
{
"item_id": 401,
"count": 2,
"unseen": true
},
{
"item_id": 901,
"count": 1,
"unseen": true
}
]
let arr = [];
itemsFetched.forEach((item) => {
itemsList.forEach((item2) => {
if (item.item_id == item2.id) {
arr.push({
"id": item.item_id,
"name": item2.name,
"count": item.count,
"img": item2.img
});
}
});
});
console.log(arr);

Categories

Resources