Consider the following data:
let data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, ] }
];
I'm trying to push something to the nested bar array on index 1 using the spread syntax (...).
So the final array should become:
[
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, 'new-item' ] }
]
Normally, we'll just use push: data[1].bar.push(0), but I need a spread solution
I've tried to use this approach:
How to push new elements to a nested array of objects in JavaScript using spread syntax
data = [ ...data, {[1]: { ...data[1], bar: [ ...data[1].bar, 'new-item' ] } }]
But this will append another object with a single key 1, it does not alter data[1].
Then, I've tried to use Object.assign() but again ended up with a new index:
Replace array entry with spread syntax in one line of code?
data = [ ...data, Object.assign({}, data[1], { bar }})
tl;dr, How do I append something to an array, part of an object, that's inside an array of objects, using the spread syntax?
Please link me to a duplicate, or provide a way to do this
Playground:
let data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9 ] }
];
// 'Regular' push method
// data[1].bar.push(0);
// Using spread reassign
// data = [ ...data, {[1]: { ...data[1], bar: [ ...data[1].bar, 'new-item' ] } }]
// Using Object.assign
data = [ ...data, Object.assign({}, data[1], {bar: [ 'new-item' ] } ) ];
console.log(data)
You could take an outer Object.assign with an array as target and an object with the index as key.
let
data = [
{ foo: true, bar: [1, 2, 3] },
{ foo: true, bar: [8, 9] }
];
data = Object.assign(
[...data], // target array
{ 1: { // array index as key
...data[1],
bar: [...data[1].bar, 'new-item']
} }
);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Object.assign on the array itself.
let data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, ] }
];
data = Object.assign([...data], {1: { ...data[1], bar: [...data[1].bar, 'new-item']}});
console.log(data);
I don't think this is more readable than many other options open to you but this satisfies the requirement of "using object spread".
let data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, ] },
{ foo: false, bar: [ ] }
];
let idx = 1;
let newData = [
...data.slice(0, idx),
{
...data[idx],
bar: [ ...data[idx].bar, 'new-item' ]
},
...data.slice(idx+1)
];
console.log(newData);
This will first take your data and cut the array up to the item you wish to replace (index 1). A new object follows, then the rest of the array (if any) follows.
First, treat the array like an object with numbered keys, and use the spread operator and override the necessary index. Then, use Object.values() to treat it like an array.
let data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, ] }
]
data = Object.values({ ...data, 1: { ...data[1], bar: [ ...data[1].bar, 'new-item' ] } })
console.log(data)
In this particular case, if the index you need to alter is near the beginning of the array, you can also use an IIFE to allow for a destructuring approach, like this:
let data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, ] }
]
data = (([first, {bar, ...rest}]) => [first, {...rest, bar:[...bar, 'new-item']}])(data)
console.log(data)
You can just assign the first element index to the modified element that uses Array#map and the spread operator as follows:
const data = [
{ foo: true, bar: [ 1, 2, 3 ] },
{ foo: true, bar: [ 8, 9, ] }
];
data[1] = [data[1]].map(({foo,bar}) => ({foo,bar:[...bar,"some new value"]}))[0];
console.log( data );
Related
I've seen this problem explained when only needing the values of one key, but not how to do it when wanting the values of multiple keys.
The object has the following structure:
objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
I want my output to look like this:
newObj = [
{ foo: [1, 3, 5] },
{ bar: [2, 4, 6] },
];
Or even
newObj = [[1, 3, 5], [2, 4, 6]];
I've tried something like this:
const newObj = objArray.map(({ foo, bar }) => ( foo, bar ));
###Logic
Since you need to listen to keys in an object, which in turn is a part of an array, you need multiple loops.
Parent Loop: On array to get individual object
Child Loop: Loop on keys of current object
Using this, you can create a hashmap. If you use array here, you will have to search based on keys to push new value. hashmap reduces this effort.
At the end, you have a single object with key and values as an array. You can loop over keys and formulate your output
const objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];
const result = objArray.reduce((acc, item) => {
Object.entries(item).forEach(([key, value]) => {
acc[key] = acc[key] || [];
acc[key].push(value)
})
return acc
}, {})
const finalValue = Object.entries(result).map(([key, value]) => ({ [key]: value }))
console.log(finalValue)
let objArray = [{
foo: 1,
bar: 2
}, {
foo: 3,
bar: 4
}, {
foo: 5,
bar: 6
}];
// 1. Get the keys
const keys = Object.keys(objArray[0]);
// Use the keys to read values from each object
const output = objArray.reduce((acc, curr) => {
keys.forEach(key => {
// if the key exists update, else initialize it
acc[key] = acc[key] ? acc[key].concat(curr[key]) : [curr[key]]
})
return acc
},{})
// Your outputs ow you want them.
console.log(output)
console.log(Object.values(output))
I have an array like this :
[
{
name: 'foo',
nestedArray: [
{
name: 'bar',
nestedArray: []
}
]
}
]
What's the best way to have a flatten array that looks like this ?
[
{
name: 'foo',
nestedArray: [
{
name: 'bar',
nestedArray: []
}
]
},
{
name: ' bar',
nestedArray: []
}
]
You can try by iterating input array and moving out nested array objects into outer one. I hope this will work as per your expectation :
// Input array
const inputArray = [
{
name: 'foo',
nestedArray: [
{
name: 'bar',
nestedArray: []
}
]
}
];
// Result array
const res = [];
// Iterating input array and moving out nested array objects into outer one.
inputArray.forEach((obj) => {
res.push(obj);
obj.nestedArray.forEach((nestedObj) => {
res.push(nestedObj);
});
});
// Assigning result
console.log(res);
Given the following array:
foos = [
{
id: 0,
bar: ['a','b','c']
},
{
id: 1,
bar: ['a','b','d']
},
{
id: 2,
bar: ['a','c']
},
]
Using reduce, how can I achieve the following?:
bars == ['a','b','c','d']
I've tried:
foo.reduce((bars, foo) => bars.add(foo.bar), new Set())
But it results in a set of objects:
Set { {0: 'a', 1: 'b', 2: 'c'}, {0: 'a', 1: 'b', 2: 'd'}{0: 'a', 1: 'c'}}
And:
foos.reduce((bars, foo) => foo.bar.forEach(bar => bars.add(bar)), new Set())
But the forEach has no access to the bars set.
Instead of creating a Set inside your reduce. You could just reduce all bar arrays into a single one and pass that to your Set constructor.
const foos = [
{
id: 0,
bar: ['a','b','c']
},
{
id: 1,
bar: ['a','b','d']
},
{
id: 2,
bar: ['a','c']
},
];
const bars = new Set(foos.reduce((all, foo) => [...all, ...foo.bar], []));
console.log(...bars);
With flatMap:
const foos = [
{
id: 0,
bar: ['a','b','c']
},
{
id: 1,
bar: ['a','b','d']
},
{
id: 2,
bar: ['a','c']
},
];
const bars = new Set(foos.flatMap(foo => foo.bar));
console.log(...bars);
You can concat the bar property in the accumulator, and use .filter method to make the values unique:
const foos = [{
id: 0,
bar: ['a', 'b', 'c']
},
{
id: 1,
bar: ['a', 'b', 'd']
},
{
id: 2,
bar: ['a', 'c']
},
];
const bars = foos
.reduce((acc, itm) => acc.concat(itm.bar), [])
.filter((i, x, s) => s.indexOf(i) === x);
console.log(...bars);
I have two arraysmetaObjects and justObjects.
These Objects in both arrays have the id property in common.
I would like to create a new array that combines properties from the objects in the different arrays
const metaObjects = [
{
id: 1,
metaProp: "metaProp1"
},
{
id: 2,
metaProp: "metaProp2"
}
];
const justObjects = [
{
id: 1,
justProp: "justProp1"
},
{
id: 2,
justProp: "justProp2"
}
];
This is the outcome I expect
const result= [
{
id: 1,
metaProp: "metaProp1",
justProp: "justProp1"
},
{
id: 2,
metaProp: "metaProp2",
justProp: "justProp2"
}
];
I have tried to implement map of map to achieve this
const combinedObject = justObjects.map(_w => {
return metaObjects.map(_m => {
if (_w.id === _m.id) {
return { ..._m, ..._w };
}
});
}, metaObjects);
console.log(combinedObject);
But I get the following error
[ [ { id: 1, metaProp: 'metaProp1', justProp: 'justProp1' },
undefined ],
[ undefined,
{ id: 2, metaProp: 'metaProp2', justProp: 'justProp2' } ] ]
I am not sure why each array has an undefined in the inner arrays.
Also I need to flatten the arrays so that they are close to the expected results above.
I have heard about the composable lens functions of ramda
Could that be used here?
This is fairly similar to the answer from customcommander, but chooses to use groupBy and values rather than sortBy and groupWith. This feels more logical to me, especially avoiding an unnecessary sort call.
const {pipe, concat, groupBy, prop, values, map, mergeAll} = R
const joinOnId = pipe
( concat
, groupBy (prop ('id'))
, values
, map (mergeAll)
)
const metaObjects =
[ { id: 1, metaProp: "metaProp1" }
, { id: 2, metaProp: "metaProp2" }
, { id: 3, metaProp: "metaProp3" } // unique to `meta`
]
const justObjects =
[ { id: 1, justProp: "justProp1" }
, { id: 2, justProp: "justProp2" }
, { id: 4, justProp: "justProp4" } // unique to `just`
]
console.log
( joinOnId (metaObjects, justObjects)
)
.as-console-wrapper {
max-height: 100vh !important;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Note that this can easily be adjusted to accept different property name:
const joinOn = (propName) =>
pipe
( concat
, groupBy (prop (propName))
, values
, map (mergeAll)
)
// ...
const joinOnId = joinOn ('id')
or to use any common key-generation function:
const joinOn = (keyFn) =>
pipe
( concat
, groupBy (keyFn)
, values
, map (mergeAll)
)
// ...
const joinOnId = joinOn (prop ('id'))
You can search for the object to merge with find() and then use Object.assign() to merge them together. This assumes that the object already exists in metaObjects, if it doesn't you'll need to decide what to do in that case.
const metaObjects = [
{
id: 1,
metaProp: "metaProp1"
},
{
id: 2,
metaProp: "metaProp2"
}
];
const justObjects = [
{
id: 1,
justProp: "justProp1"
},
{
id: 2,
justProp: "justProp2"
}
];
justObjects.forEach(item => {
let toMerge = metaObjects.find(obj => obj.id === item.id)
Object.assign(toMerge, item)
})
console.log(metaObjects)
If metaObjects is potentially large, it would be better to store it as an object keyed to id. Then you could look it up directly without having to search each time.
If you don't want to alter metaObjects, you can map() over justObjects and create a new array:
const metaObjects = [
{
id: 1,
metaProp: "metaProp1"
},
{
id: 2,
metaProp: "metaProp2"
}
];
const justObjects = [
{
id: 1,
justProp: "justProp1"
},
{
id: 2,
justProp: "justProp2"
}
];
let newArray = justObjects.map(item => {
let toMerge = metaObjects.find(obj => obj.id === item.id)
return Object.assign({}, toMerge, item)
})
// metaObjects unaffected
console.log(newArray)
I think you could simply combine the two arrays together, group objects by id (you need to sort first) and finally merge each group:
const {
map,
mergeAll,
groupWith,
eqBy,
prop,
concat,
sortBy,
pipe
} = R;
const metaObjects = [
{ id: 1,
metaProp: "metaProp1" },
{ id: 2,
metaProp: "metaProp2" }];
const justObjects = [
{ id: 1,
justProp: "justProp1" },
{ id: 2,
justProp: "justProp2" }];
const process = pipe(
concat,
sortBy(prop('id')),
groupWith(eqBy(prop('id'))),
map(mergeAll));
console.log(
process(metaObjects, justObjects)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
I would use Array.prototype.reduce() or a for loop to convert one of them from an Array of objects with an id property to an object of objects, using the id as the key:
const merged = metaObjects.reduce((acc, cur) => {
acc[cur.id] = cur;
return acc;
}, {});
Or:
const merged = {};
for (const obj of metaObjects) {
merged[obj.id] = obj;
}
Then, iterate the other one while merging each of its entries in the object we have just created above:
justObjects.forEach((obj) => {
merged[obj.id] = Object.assign({}, merged[obj.id], obj);
});
Lastly, just convert it back to an Array using Object.values:
Object.values(merged);
Example:
const metaObjects = [{
id: 1,
metaProp: "metaProp1"
},{
id: 2,
metaProp: "metaProp2"
}];
const justObjects = [{
id: 1,
justProp: "justProp1"
},{
id: 2,
justProp: "justProp2"
},{
id: 3,
justProp: "justProp3"
}];
// Create an object of one of the two using is id property:
/*
// Alternative using reduce:
const merged = metaObjects.reduce((acc, cur) => {
acc[cur.id] = cur;
return acc;
}, {});
*/
// Alternative using a for loop:
const merged = {};
for (const obj of metaObjects) {
merged[obj.id] = obj;
}
// Iterate the other one and merge it with the map you have just created:
justObjects.forEach((obj) => {
merged[obj.id] = Object.assign({}, merged[obj.id], obj);
});
// Convert it back to an Array of objects:
console.log(Object.values(merged));
.as-console-wrapper {
max-height: 100vh !important;
}
Note this will work even if any of the two objects contain entries for an id that is not present in the other.
Suppose I have a JS object with multiple objects with the same properties.
EDIT: I modified the outer braces to square brackets to reflect what the actual object is. The accepted answer is in the comments.
var object = [
{
id: 1,
foo: 'bar'
},
{
id: 2,
foo: 'bar2'
},
{
id: 3,
foo: 'bar3'
},
{
id: 4,
foo: 'bar4'
}
];
How would I get the object with a specific id e.g. id == 1, something similar to the Rails method ActiveRecord::Relation.where(id: 1)?
You need to make an array of objects for search and try this like,
var object = [{ // make array by using [ and ]
id: 1,
foo: 'bar'
}, {
id: 2,
foo: 'bar2'
}, {
id: 3,
foo: 'bar3'
}, {
id: 4,
foo: 'bar4'
}];
function searchByKey(obj, key) {
for (var i in obj) {
if (obj[i].id == key) {
return obj[i];
}
}
return "Not found";
}
console.log(searchByKey(object,1));
console.log(searchByKey(object,4));
Live Demo