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.
Related
I have this array of objects, within it I have another array of objects:
[
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
How to get flat array of country like this:
[
{ id: "5a60626f1d41c80c8d3f8a85" },
{ id: "5a6062661d41c80c8b2f0413" },
{ id: "5a60626f1d41c80c8d3f8a83" },
{ id: "5a60626f1d41c80c8d3f8a84" }
];
without using a forEach and a temp variable?
When I did:
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
})
I got the same structure back.
Latest edit
All modern JS environments now support Array.prototype.flat and Array.prototype.flatMap
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.flatMap(
(elem) => elem.country
)
)
Old answer
No need for any ES6 magic, you can just reduce the array by concatenating inner country arrays.
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => arr.concat(elem.country), []
)
)
If you want an ES6 feature (other than an arrow function), use array spread instead of the concat method:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => [...arr, ...elem.country], []
)
)
Note: These suggestions would create a new array on each iteration.
For efficiency, you have to sacrifice some elegance:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => {
for (const c of elem.country) {
arr.push(c);
}
return arr;
}, []
)
)
const raw = [
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
const countryIds = raw
.map(x => x.country)
.reduce((acc, curr) => {
return [
...acc,
...curr.map(x => x.id)
];
}, []);
console.log(countryIds)
This, works, just concat the nested arrays returned by your solution
let arr = [{ "id": 1,
"country": [{
"id": "5a60626f1d41c80c8d3f8a85",
},
{
"id": "5a6062661d41c80c8b2f0413",
}
]
},
{
"id": 2,
"country": [{
"id": "5a60626f1d41c80c8d3f8a83",
},
{
"id": "5a60626f1d41c80c8d3f8a84",
}
]
}
];
//If you want an array of country objects
console.log([].concat.apply(...(arr || []).map(o=> o.country)))
//If you can an array od country ids
console.log([].concat.apply(...(arr || []).map(o=> o.country.map(country => country.id))))
Ayush Gupta's solution will work for this case. But I would like to provide other solution.
const arr = [
{
id: 1,
country: [
{
id: '5a60626f1d41c80c8d3f8a85'
},
{
id: '5a6062661d41c80c8b2f0413'
}
]
},
{
id: 2,
country: [
{
id: '5a60626f1d41c80c8d3f8a83'
},
{
id: '5a60626f1d41c80c8d3f8a84'
}
]
}
];
const ids = arr.reduce(
(acc, {country}) => [
...acc,
...country.map(({id}) => ({
id
}))
],
[]
);
console.log(ids);
For JSON string data, it can be done during parsing too :
var ids = [], json = '[{"id":1,"country":[{"id":"5a60626f1d41c80c8d3f8a85"},{"id":"5a6062661d41c80c8b2f0413"}]},{"id":2,"country":[{"id":"5a60626f1d41c80c8d3f8a83"},{"id":"5a60626f1d41c80c8d3f8a84"}]}]';
JSON.parse(json, (k, v) => v.big && ids.push(v));
console.log( ids );
I am not sure why noone mentioned flat() (probably for large arrays, it might be less performant)
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
}).flat()
I've tried modifying some of the similar solutions on here but I keep getting stuck, I believe I have part of this figured out however, the main caveat is that:
Some of the objects have extra keys, which renders my object comparison logic useless.
I am trying to compare two arrays of objects. One array is the original array, and the other array contains the items I want deleted from the original array. However there's one extra issue in that the second array contains extra keys, so my comparison logic doesn't work.
An example would make this easier, let's say I have the following two arrays:
const originalArray = [{id: 1, name: "darnell"}, {id: 2, name: "funboi"},
{id: 3, name: "jackson5"}, {id: 4, name: "zelensky"}];
const itemsToBeRemoved = [{id: 2, name: "funboi", extraProperty: "something"},
{id: 4, name: "zelensky", extraProperty: "somethingelse"}];
after running the logic, my final output should be this array:
[{id: 1, name: "darnell"}, {id: 3, name: "jackson5"}]
And here's the current code / logic that I have, which compares but doesn't handle the extra keys. How should I handle this? Thank you in advance.
const prepareArray = (arr) => {
return arr.map((el) => {
if (typeof el === "object" && el !== null) {
return JSON.stringify(el);
} else {
return el;
}
});
};
const convertJSON = (arr) => {
return arr.map((el) => {
return JSON.parse(el);
});
};
const compareArrays = (arr1, arr2) => {
const currentArray = [...prepareArray(arr1)];
const deletedItems = [...prepareArray(arr2)];
const compared = currentArray.filter((el) => deletedItems.indexOf(el) === -1);
return convertJSON(compared);
};
How about using filter and some? You can extend the filter condition on select properties using &&.
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
console.log(
originalArray.filter(item => !itemsToBeRemoved.some(itemToBeRemoved => itemToBeRemoved.id === item.id))
)
Or you can generalise it as well.
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
function filterIfSubset(originalArray, itemsToBeRemoved) {
const filteredArray = [];
for (let i = 0; i < originalArray.length; i++) {
let isSubset = false;
for (let j = 0; j < itemsToBeRemoved.length; j++) {
// check if whole object is a subset of the object in itemsToBeRemoved
if (Object.keys(originalArray[i]).every(key => originalArray[i][key] === itemsToBeRemoved[j][key])) {
isSubset = true;
}
}
if (!isSubset) {
filteredArray.push(originalArray[i]);
}
}
return filteredArray;
}
console.log(filterIfSubset(originalArray, itemsToBeRemoved));
Another simpler variation of the second approach:
const originalArray = [
{ id: 1, name: 'darnell' },
{ id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' },
];
const itemsToBeRemoved = [
{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' },
];
const removeSubsetObjectsIfExists = (originalArray, itemsToBeRemoved) => {
return originalArray.filter(item => {
const isSubset = itemsToBeRemoved.some(itemToBeRemoved => {
return Object.keys(item).every(key => {
return item[key] === itemToBeRemoved[key];
});
});
return !isSubset;
});
}
console.log(removeSubsetObjectsIfExists(originalArray, itemsToBeRemoved));
The example below is a reusable function, the third parameter is the key to which you compare values from both arrays.
Details are commented in example
const arr=[{id:1,name:"darnell"},{id:2,name:"funboi"},{id:3,name:"jackson5"},{id:4,name:"zelensky"}],del=[{id:2,name:"funboi",extraProperty:"something"},{id:4,name:"zelensky",extraProperty:"somethingelse"}];
/** Compare arrayA vs. delArray by a given key's value.
--- ex. key = 'id'
**/
function deleteByKey(arrayA, delArray, key) {
/* Get an array of only the values of the given key from delArray
--- ex. delList = [1, 2, 3, 4]
*/
const delList = delArray.map(obj => obj[key]);
/* On every object of arrayA compare delList values vs
current object's key's value
--- ex. current obj[id] = 2
--- [1, 2, 3, 4].includes(obj[id])
Any match returns an empty array and non-matches are returned
in it's own array.
--- ex. ? [] : [obj]
The final return is a flattened array of the non-matching objects
*/
return arrayA.flatMap(obj => delList.includes(obj[key]) ? [] : [obj]);
};
console.log(deleteByKey(arr, del, 'id'));
let ff = [{ id: 1, name: 'darnell' }, { id: 2, name: 'funboi' },
{ id: 3, name: 'jackson5' },
{ id: 4, name: 'zelensky' }]
let cc = [{ id: 2, name: 'funboi', extraProperty: 'something' },
{ id: 4, name: 'zelensky', extraProperty: 'somethingelse' }]
let ar = []
let out = []
const result = ff.filter(function(i){
ar.push(i.id)
cc.forEach(function(k){
out.push(k.id)
})
if(!out.includes(i.id)){
// console.log(i.id, i)
return i
}
})
console.log(result)
How would I find all values by specific key in a deep nested object?
For example, if I have an object like this:
const myObj = {
id: 1,
children: [
{
id: 2,
children: [
{
id: 3
}
]
},
{
id: 4,
children: [
{
id: 5,
children: [
{
id: 6,
children: [
{
id: 7,
}
]
}
]
}
]
},
]
}
How would I get an array of all values throughout all nests of this obj by the key of id.
Note: children is a consistent name, and id's won't exist outside of a children object.
So from the obj, I would like to produce an array like this:
const idArray = [1, 2, 3, 4, 5, 6, 7]
This is a bit late but for anyone else finding this, here is a clean, generic recursive function:
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object')
? acc.concat(findAllByKey(value, keyToFind))
: acc
, [])
}
// USAGE
findAllByKey(myObj, 'id')
You could make a recursive function like this:
idArray = []
function func(obj) {
idArray.push(obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
Snippet for your sample:
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
}
idArray = []
function func(obj) {
idArray.push(obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
func(myObj)
console.log(idArray)
I found steve's answer to be most suited for my needs in extrapolating this out and creating a general recursive function. That said, I encountered issues when dealing with nulls and undefined values, so I extended the condition to accommodate for this. This approach uses:
Array.reduce() - It uses an accumulator function which appends the value's onto the result array. It also splits each object into it's key:value pair which allows you to take the following steps:
Have you've found the key? If so, add it to the array;
If not, have I found an object with values? If so, the key is possibly within there. Keep digging by calling the function on this object and append the result onto the result array; and
Finally, if this is not an object, return the result array unchanged.
Hope it helps!
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
}
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object' && value)
? acc.concat(findAllByKey(value, keyToFind))
: acc
, []) || [];
}
const ids = findAllByKey(myObj, 'id');
console.log(ids)
You can make a generic recursive function that works with any property and any object.
This uses Object.entries(), Object.keys(), Array.reduce(), Array.isArray(), Array.map() and Array.flat().
The stopping condition is when the object passed in is empty:
const myObj = {
id: 1,
anyProp: [{
id: 2,
thing: { a: 1, id: 10 },
children: [{ id: 3 }]
}, {
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{ id: 7 }]
}]
}]
}]
};
const getValues = prop => obj => {
if (!Object.keys(obj).length) { return []; }
return Object.entries(obj).reduce((acc, [key, val]) => {
if (key === prop) {
acc.push(val);
} else {
acc.push(Array.isArray(val) ? val.map(getIds).flat() : getIds(val));
}
return acc.flat();
}, []);
}
const getIds = getValues('id');
console.log(getIds(myObj));
Note: children is a consistent name, and id's wont exist outside
of a children object.
So from the obj, I would like to produce an array like this:
const idArray = [1, 2, 3, 4, 5, 6, 7]
Given that the question does not contain any restrictions on how the output is derived from the input and that the input is consistent, where the value of property "id" is a digit and id property is defined only within "children" property, save for case of the first "id" in the object, the input JavaScript plain object can be converted to a JSON string using JSON.stringify(), RegExp /"id":\d+/g matches the "id" property and one or more digit characters following the property name, which is then mapped to .match() the digit portion of the previous match using Regexp \d+ and convert the array value to a JavaScript number using addition operator +
const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};
let res = JSON.stringify(myObject).match(/"id":\d+/g).map(m => +m.match(/\d+/));
console.log(res);
JSON.stringify() replacer function can alternatively be used to .push() the value of every "id" property name within the object to an array
const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};
const getPropValues = (o, prop) =>
(res => (JSON.stringify(o, (key, value) =>
(key === prop && res.push(value), value)), res))([]);
let res = getPropValues(myObject, "id");
console.log(res);
Since the property values of the input to be matched are digits, all the JavaScript object can be converted to a string and RegExp \D can be used to replace all characters that are not digits, spread resulting string to array, and .map() digits to JavaScript numbers
let res = [...JSON.stringify(myObj).replace(/\D/g,"")].map(Number)
Using recursion.
const myObj = { id: 1, children: [ { id: 2, children: [ { id: 3 } ] }, { id: 4, children: [ { id: 5, children: [ { id: 6, children: [ { id: 7, } ] } ] } ] }, ]},
loop = (array, key, obj) => {
if (!obj.children) return;
obj.children.forEach(c => {
if (c[key]) array.push(c[key]); // is not present, skip!
loop(array, key, c);
});
},
arr = myObj["id"] ? [myObj["id"]] : [];
loop(arr, "id", myObj);
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can make a recursive function with Object.entries like so:
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
};
function findIds(obj) {
const entries = Object.entries(obj);
let result = entries.map(e => {
if (e[0] == "children") {
return e[1].map(child => findIds(child));
} else {
return e[1];
}
});
function flatten(arr, flat = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, flat);
} else {
flat.push(value);
}
}
return flat;
}
return flatten(result);
}
var ids = findIds(myObj);
console.log(ids);
Flattening function from this answer
ES5 syntax:
var myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
};
function findIds(obj) {
const entries = Object.entries(obj);
let result = entries.map(function(e) {
if (e[0] == "children") {
return e[1].map(function(child) {
return findIds(child)
});
} else {
return e[1];
}
});
function flatten(arr, flat = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, flat);
} else {
flat.push(value);
}
}
return flat;
}
return flatten(result);
}
var ids = findIds(myObj);
console.log(ids);
let str = JSON.stringify(myObj);
let array = str.match(/\d+/g).map(v => v * 1);
console.log(array); // [1, 2, 3, 4, 5, 6, 7]
We use object-scan for a lot of our data processing needs now. It makes the code much more maintainable, but does take a moment to wrap your head around. Here is how you could use it to answer your question
// const objectScan = require('object-scan');
const find = (data, needle) => objectScan([needle], { rtn: 'value' })(data);
const myObj = { id: 1, children: [{ id: 2, children: [ { id: 3 } ] }, { id: 4, children: [ { id: 5, children: [ { id: 6, children: [ { id: 7 } ] } ] } ] }] };
console.log(find(myObj, '**.id'));
// => [ 7, 6, 5, 4, 3, 2, 1 ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
import {flattenDeep} from 'lodash';
/**
* Extracts all values from an object (also nested objects)
* into a single array
*
* #param obj
* #returns
*
* #example
* const test = {
* alpha: 'foo',
* beta: {
* gamma: 'bar',
* lambda: 'baz'
* }
* }
*
* objectFlatten(test) // ['foo', 'bar', 'baz']
*/
export function objectFlatten(obj: {}) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(objectFlatten(value));
} else {
result.push(value);
}
}
return flattenDeep(result);
}
Below solution is generic which will return all values by matching nested keys as well e.g for below json object
{
"a":1,
"b":{
"a":{
"a":"red"
}
},
"c":{
"d":2
}
}
to find all values matching key "a" output should be return
[1,{a:"red"},"red"]
const findkey = (obj, key) => {
let arr = [];
if (isPrimitive(obj)) return obj;
for (let [k, val] of Object.entries(obj)) {
if (k === key) arr.push(val);
if (!isPrimitive(val)) arr = [...arr, ...findkey(val, key)];
}
return arr;
};
const isPrimitive = (val) => {
return val !== Object(val);
};
So, if I have two arrays...
const arr1 = [ { id: 1: newBid: true } ];
const arr2 = [ { id: 1, newBid: false }, { id: 2, newBid: false } ];
I want to wind up with an array that is like this
[ { id: 1, newBid: false }, { id: 2, newBid: false } ]
BUT... I want the { id: 1, newBid: true } to be from arr1 and not arr2
I was using Lodash uniqBy(arr1, arr2, ['id']), but it deletes the 1st occurance, not the 2nd
You should use lodash mergeWith function.
const arr1 = [{
id: 1,
newBid: true
}];
const arr2 = [{
id: 1,
newBid: false
}, {
id: 2,
newBid: false
}];
function customizer(firstValue, secondValue) {
if(firstValue)
return firstValue;
else
return secondValue;
}
console.log(_.mergeWith(arr1, arr2, customizer));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>
I find using an object as a map to be the easiest way to deal with this kind of problems.
const arr1 = [ { id: 1, newBid: true } ];
const arr2 = [ { id: 1, newBid: false }, { id: 2, newBid: false } ];
const map = {};
function execute(array) {
for(let i = 0; i < array.length; i++) {
const item = array[i];
map[item.id] = item;
}
}
execute(arr1);
execute(arr2);
console.log(Object.values(map))
I have this array of objects, within it I have another array of objects:
[
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
How to get flat array of country like this:
[
{ id: "5a60626f1d41c80c8d3f8a85" },
{ id: "5a6062661d41c80c8b2f0413" },
{ id: "5a60626f1d41c80c8d3f8a83" },
{ id: "5a60626f1d41c80c8d3f8a84" }
];
without using a forEach and a temp variable?
When I did:
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
})
I got the same structure back.
Latest edit
All modern JS environments now support Array.prototype.flat and Array.prototype.flatMap
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.flatMap(
(elem) => elem.country
)
)
Old answer
No need for any ES6 magic, you can just reduce the array by concatenating inner country arrays.
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => arr.concat(elem.country), []
)
)
If you want an ES6 feature (other than an arrow function), use array spread instead of the concat method:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => [...arr, ...elem.country], []
)
)
Note: These suggestions would create a new array on each iteration.
For efficiency, you have to sacrifice some elegance:
const data=[{id:1,country:[{id:"5a60626f1d41c80c8d3f8a85"},{id:"5a6062661d41c80c8b2f0413"}]},{id:2,country:[{id:"5a60626f1d41c80c8d3f8a83"},{id:"5a60626f1d41c80c8d3f8a84"}]}];
console.log(
data.reduce(
(arr, elem) => {
for (const c of elem.country) {
arr.push(c);
}
return arr;
}, []
)
)
const raw = [
{
id: 1,
country: [
{
id: "5a60626f1d41c80c8d3f8a85"
},
{
id: "5a6062661d41c80c8b2f0413"
}
]
},
{
id: 2,
country: [
{
id: "5a60626f1d41c80c8d3f8a83"
},
{
id: "5a60626f1d41c80c8d3f8a84"
}
]
}
];
const countryIds = raw
.map(x => x.country)
.reduce((acc, curr) => {
return [
...acc,
...curr.map(x => x.id)
];
}, []);
console.log(countryIds)
This, works, just concat the nested arrays returned by your solution
let arr = [{ "id": 1,
"country": [{
"id": "5a60626f1d41c80c8d3f8a85",
},
{
"id": "5a6062661d41c80c8b2f0413",
}
]
},
{
"id": 2,
"country": [{
"id": "5a60626f1d41c80c8d3f8a83",
},
{
"id": "5a60626f1d41c80c8d3f8a84",
}
]
}
];
//If you want an array of country objects
console.log([].concat.apply(...(arr || []).map(o=> o.country)))
//If you can an array od country ids
console.log([].concat.apply(...(arr || []).map(o=> o.country.map(country => country.id))))
Ayush Gupta's solution will work for this case. But I would like to provide other solution.
const arr = [
{
id: 1,
country: [
{
id: '5a60626f1d41c80c8d3f8a85'
},
{
id: '5a6062661d41c80c8b2f0413'
}
]
},
{
id: 2,
country: [
{
id: '5a60626f1d41c80c8d3f8a83'
},
{
id: '5a60626f1d41c80c8d3f8a84'
}
]
}
];
const ids = arr.reduce(
(acc, {country}) => [
...acc,
...country.map(({id}) => ({
id
}))
],
[]
);
console.log(ids);
For JSON string data, it can be done during parsing too :
var ids = [], json = '[{"id":1,"country":[{"id":"5a60626f1d41c80c8d3f8a85"},{"id":"5a6062661d41c80c8b2f0413"}]},{"id":2,"country":[{"id":"5a60626f1d41c80c8d3f8a83"},{"id":"5a60626f1d41c80c8d3f8a84"}]}]';
JSON.parse(json, (k, v) => v.big && ids.push(v));
console.log( ids );
I am not sure why noone mentioned flat() (probably for large arrays, it might be less performant)
(data || []).map(o=>{
return o.country.map(o2=>({id: o2.id}))
}).flat()