I have the following code and test data:
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) => {
return (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj;
});
}
const obj =
[
{
a: 1,
c: [
{
d: 1,
e: 'string',
f: [
{
value: 0,
},
{
value: 1,
}
],
},
],
},
{
a: 2,
c: [
{
d: 2,
e: 'string',
f: [
{
value: 3,
},
{
value: 4,
}
],
},
],
},
];
console.log(obj);
const fs = obj.map(o => getNestedObject(o, ['c', 'f']));
console.log(fs);
What I want to do is given the array of objects shown below, I want to get only the property called f from every object in the array. So, basically end result should be array of f values of every object. Since 'f' is an array, I would highly appreciate the end result to be just one array with elements from all 'f' properties, so kind of every of these 'f' to be spread out, so I have one array. My above getNestedObject function does not seem to work, as when the console.log statement below returns the whole object. Any ideas how to do this in JS?
So basically the end result should be:
[{ value: 0 }, { value: 1 }, { value: 3 }, {value: 4 }]
You can combine reduce() with map(). Basically reduce your main array into an flattened array of all the c.f items. This checks for the c property just in case the object doesn't have it:
const obj = [{a: 1,c: [{d: 1,e: 'string',f: [{value: 0,},{value: 1,}],},],},{a: 2,c: [{d: 2,e: 'string',f: [{value: 3,},{value: 4,}],},],},];
let Fs = obj.reduce((arr, item) =>
item.c
? arr.concat(...item.c.map(itemc => itemc.f )) // concat for flattened rather than nested arrays
: arr
, []);
console.log(Fs)
Here's a fast iterative solution that won't overflow the stack, makes no assumptions about target result values being arrays (only spreads if they are) and doesn't hard-code child key names (it'll explore any values that are arrays).
This can also work if the target has children matching the key that you'd like to include in the search (swap else if with if).
const get = (data, target) => {
const result = [];
const stack = [data];
while (stack.length) {
const curr = stack.pop();
for (const o of curr) {
for (const k in o) {
if (k === target) {
if (Array.isArray(o[k])) {
result.push(...o[k]);
}
else {
result.push(o[k]);
}
}
else if (Array.isArray(o[k])) {
stack.push(o[k]);
}
}
}
}
return result;
};
const obj =
[
{
a: 1,
c: [
{
d: 1,
e: 'string',
f: [
{
value: 0,
},
{
value: 1,
}
],
},
],
},
{
a: 2,
c: [
{
d: 2,
e: 'string',
f: [
{
value: 3,
},
{
value: 4,
}
],
},
],
},
];
console.log(get(obj, "f"));
You can recursively traverse any objects and arrays to fetch a given property. This works at any depth and doesn't care about the structure of the objects:
const obj=[{a:1,c:[{d:1,e:"string",f:[{value:0},{value:1}]}]},{a:2,c:[{d:2,e:"string",f:[{value:3},{value:4}]}]}];
//curried function to grab a property by name off some object or array
function grab(prop) {
//naming the inner function means it can be called recursively by name
return function recursiveGrab(target) {
if (Array.isArray(target)) {
const arrayResult = target
.filter(x => typeof x === "object") //remove non-objects (and non-arrays)
.filter(Boolean) //remove null
.map(recursiveGrab); //recursively call for the remaining objects
return flatten(arrayResult); //return a single dimensional array
}
//an object has the property - return it
if (prop in target) {
return target[prop];
}
//object doesn't have the property - check all values
return recursiveGrab(Object.values(target));
}
}
//small helper function. It's separated only to keep the logic for the traversal clear
function flatten(arr) {
return arr.reduce((acc, curr) => acc.concat(curr), [])
}
const grabF = grab('f');
console.log(grabF(obj));
I did not notice that f was always inside c. I have this recursive and dirty looking solution that works with f being inside any of the fields
const objArray = [
{
a: 1,
c: [
{
d: 1,
e: 'string',
f: [
{
value: 0,
},
{
value: 1,
}
],
},
],
d: [
{
d: 1,
e: 'string',
f: [
{
value: 'd',
},
{
value: 'd1',
}
],
},
],
},
{
a: 2,
c: [
{
d: 2,
e: 'string',
f: [
{
value: 3,
},
{
value: 4,
}
],
},
],
e: [
{
d: 1,
e: 'string',
f: [
{
value: 'e',
},
{
value: 'e1',
}
],
},
],
}
]
const getFObject = (obj) => {
let fObj = [];
Object.keys(obj).some(key => {
if (key === 'f') {
fObj = obj[key];
return true;
}
if (Array.isArray(obj[key])) {
obj[key].forEach(nestedObj => {
fObj = fObj.concat(getFObject(nestedObj))
});
}
return false;
});
return fObj;
}
const newArray = objArray.reduce((acc, obj) => {
return acc.concat(getFObject(obj))
}, []);
console.log(newArray)
Related
So I am trying to automate unpacking a nested Json with arrays inside and stuck at creating duplicate objects if value of key is array with length > 1
How do I do it?
Right now I am trying to achieve it with recursion
[
{
a: '1',
b: [
{
c: '3',
d: '4',
},
{
c: '5'
},
{
c: '7',
d: '8'
}
],
f: [
{
d: '6'
},
{
d: '9'
}
],
e: [
{
g: '9'
}
]
}
]
// Expect
// When creating duplicate object, those keys which repeat I want to mark as undefined, to make JSON lighter
// I also want to add 'key: number' to order those objects
[
{
a: '1',
b.c: '3',
b.d: '4',
f.d: '6',
e.g: '9',
key: 1,
},
{
a: undefined,
b.c: '5',
b.d: undefined,
f.d: '9',
e.g: undefined,
key: 2,
},
{
a: undefined,
b.c: '7',
b.d: '8',
f.d: undefined,
e.g: undefined,
key: 3,
}
]
// Code
function recurseObject(object: any, nestedKeyName: any, obj: any, count: any) {
Object.entries(object).map(([key, dataItem]: [key: string, dataItem: any]) => {
const newKeyName = nestedKeyName ? (nestedKeyName + '.' + key) : key
let currentCount = count
if (Array.isArray(dataItem)) {
obj[newKeyName] = dataItem.map((item: any) => {
const objNested: any = {}
recurseObject(item, newKeyName, objNested, currentCount)
return objNested
})
} else if (isObject(dataItem)) {
obj['key'] = currentCount
recurseObject(dataItem, newKeyName, obj, currentCount + 1)
} else {
obj[newKeyName] = dataItem
}
})
}
function rcBody(data: any): any {
if (Array.isArray(data)) {
let key = 0
return data.map((object: any) => {
const obj: any = {}
recurseObject(object, null, obj, 0)
obj['key'] = key
key += 1
return obj
})
} else if (isObject(data)) {
const obj: any = {}
recurseObject(data, null, obj, 0)
return obj
} else {
return {}
}
}
If the value of key is array of objects with more than one object, then I want to create a duplicate object.
Table I want to generate
I have bad news and good news for you:
First the bad news:
If it is in fact true, that objects/arrays can be nested even further than it is currently the case, then your data saving format doesn't really work. Let me show you why wiht the help of an example:
[
{
t: [
{
a: [
{
b: 1,
c: 2,
},
{ c: 3 },
],
},
{
a: [
{
b: 3,
c: 5,
},
],
},
],
},
];
how would you go about storing this? ... without nesting the return Array/object itself, it is extremely difficult (if not impossible), to store the date how you are wanting to)
How do you want to go about storing cases like this one here? (The example I provided is a quite tame example ... if you add some more nesting/ more objects/ arrays, it gets way more complicated.
Also is the your particular way of storing data even required? Is the way you structure your desired return array/objects relevant in future steps, or would other structures work too?
But nonetheless I have produced 2 functions ... the first one can handle 2 deep paths (so no deeper than the array/object you provided in your example) the array above would NOT work with this function
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction2(arg, currentPath = "", rtnArr = [{}], key = 0) {
if (Array.isArray(arg)) {
arg.forEach((x, index) => myFunction2(x, currentPath, rtnArr, index));
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction2(arg[x], currentPath + "." + x, rtnArr, key));
} else {
rtnArr[key] = rtnArr[key] || {};
rtnArr[key][currentPath.substring(1)] = arg;
}
return rtnArr;
}
console.log(myFunction2(x));
The next function can handle infinitely nested arrays/objects - BUT the return is not quite like you desire - (but still: all paths with value are still present in an array of objects ... and each objects, contains each unique path only once) - only in which object the path value pair appears is different. But it's probably best to just test/read the code to see how it works, what the output will be.
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction(arg, currentPath = "", rtnArr = [{}]) {
if (Array.isArray(arg)) {
arg.forEach((x) => myFunction(x, currentPath, rtnArr));
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction(arg[x], currentPath + "." + x, rtnArr));
} else {
for (let i = 0; i < rtnArr.length; i++) {
if (!rtnArr[i][currentPath.substring(1)]) {
rtnArr[i][currentPath.substring(1)] = arg;
return rtnArr
}
}
rtnArr[rtnArr.length] = {};
rtnArr[rtnArr.length - 1][currentPath.substring(1)] = arg;
}
return rtnArr;
}
console.log(myFunction(x))
At the very least, you know have an idea, how you can solve the problem with recursive functions ... and if my functions don't quite fit your purpose, you at least have a working start point, from where you can tweak and improve the functions, to a point where they fit your needs ... or take the knowledge/ideas you get from reading and understanding my code to write your own.
Edit:
Great news ... I figured out a way where you can have your structure/sorting of data and no restriction on how deeply nested the passed array/object can be.
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction2(arg, currentPath = "", rtnArr = [{}], key = []) {
if (Array.isArray(arg)) {
arg.forEach((x, index) => {
key.push(index);
myFunction2(x, currentPath, rtnArr, key);
key.pop();
});
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction2(arg[x], currentPath + "." + x, rtnArr, key));
} else {
let stringKey = key.reduce((pVal, cVal) => pVal + "." + cVal, "");
if (rtnArr.some((x) => x.key === stringKey)) {
rtnArr.filter((x) => x.key === stringKey)[0][currentPath] = arg;
} else {
rtnArr[rtnArr.length] = { key: stringKey };
rtnArr[rtnArr.length - 1][currentPath] = arg;
}
}
return rtnArr;
}
console.log(myFunction2(x));
How to check if array contains different values with React.js and typescript?
Example:
[{
name: 'John',
value: 1,
}, {
name: 'John',
value: 1,
}, {
name: 'Carla',
value: 15,
}]
I want to return false if all objects in array are same, and true if there is at least one different object.
You can't use a direct equality comparison since objects will never return equal.
Ie {} != {}, and {name: 'John', value: 1} != {name: 'John', value: 1}.
So firstly you have to decide what you're going to define as 'equal' for these objects.
Let's say for the sake of this that you use just the name field as the test for equality. So if two objects in the array have the same name field, then you'll call them equal. Then you'd define the function:
type NameValue = {name: string, value: string}
const areEqual = (obj1: NameValue, obj2: NameValue): boolean => obj1.name === obj2.name
Of course you can change this function to reflect whatever you define as 'equal'. There are npm packages to help you with deep equality checks too, or you can JSON.stringify both and check that equality
Then you can use Array.some(). Array.some() will return true if any element in the array passes a test. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
Testing if any element is not equal to the first should be sufficient.
const areNotAllEqual = yourArray.some((currentElement) => {
return !areEqual(currentElement, yourArray[0])
})
After having commented on and criticized especially the approaches based on JSON.stringify, I want to contribute something on that matter. Since meanwhile all modern JS engines seem to be aware of an object's key order (in how this object was created) and also seem to guarantee such an order for key-iteration one could write a recursive function, which for any deeply nested but JSON-conform JS-objects reestablishes a normalized key-order for such objects but leaves arrays untouched.
Passing such key-normalized objects to JSON.stringify then makes such objects comparable by their's stringified signature ...
function defaultCompare(a, b) {
return ((a < b) && -1) || ((a > b) && 1) || 0;
}
function comparePropertyNames(a, b) {
return a.localeCompare
? a.localeCompare(b)
: defaultCompare(a, b);
}
function getJsonDataWithNormalizedKeyOrder(data) {
let value;
if (Array.isArray(data)) {
value = data.map(getJsonDataWithNormalizedKeyOrder);
} else if (data && (typeof data === 'object')) {
value = Object
.getOwnPropertyNames(data)
.sort(comparePropertyNames)
.reduce((obj, key) => {
obj[key] = getJsonDataWithNormalizedKeyOrder(data[key])
return obj;
}, {});
} else {
value = data;
}
return value;
}
const objA = {
name: 'foo',
value: 1,
obj: {
z: 'z',
y: 'y',
a: {
name: 'bar',
value: 2,
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
name: 'baz',
value: 3,
obj: {
k: 'k',
i: 'i',
c: 'c',
},
arr: ['5', 6, 'B', 'A'],
}],
},
},
arr: ['Z', 'Y', 1, '2'],
};
const objB = {
arr: ['Z', 'Y', 1, '2'],
obj: {
z: 'z',
y: 'y',
a: {
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
obj: {
k: 'k',
i: 'i',
c: 'c',
},
name: 'baz',
value: 3,
arr: ['5', 6, 'B', 'A'],
}],
name: 'bar',
value: 2,
},
},
name: 'foo',
value: 1,
};
const objC = {
arr: ['Z', 'Y', 1, '2'],
obj: {
z: 'z',
y: 'y',
a: {
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
obj: {
k: 'k',
i: 'i',
c: 'c',
},
name: 'baz',
value: 3,
arr: ['5', 6, 'B', 'A'],
}],
name: 'bar',
value: 2,
},
},
name: 'foo',
value: 2,
};
console.log(
'getJsonDataWithNormalizedKeyOrder(objA) ...',
getJsonDataWithNormalizedKeyOrder(objA)
);
console.log(
'getJsonDataWithNormalizedKeyOrder(objB) ...',
getJsonDataWithNormalizedKeyOrder(objB)
);
console.log(
'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA)) ...',
JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA))
);
console.log(
'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB)) ...',
JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB))
);
console.log(
'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC)) ...',
JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC))
);
console.log(
'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA)).length ...',
JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA)).length
);
console.log(
'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB)).length ...',
JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB)).length
);
console.log(
'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC)).length ...',
JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC)).length
);
console.log(`
JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objA)
) === JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objB)
) ?`,
JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objA)
) === JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objB)
)
);
console.log(`
JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objA)
) === JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objC)
) ?`,
JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objA)
) === JSON.stringify(
getJsonDataWithNormalizedKeyOrder(objC)
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Applying the above to an approach which solves the OP's original problem in a more generic way then might look similar to the next provided lines ...
function defaultCompare(a, b) {
return ((a < b) && -1) || ((a > b) && 1) || 0;
}
function comparePropertyNames(a, b) {
return a.localeCompare
? a.localeCompare(b)
: defaultCompare(a, b);
}
function getJsonDataWithNormalizedKeyOrder(data) {
let value;
if (Array.isArray(data)) {
value = data.map(getJsonDataWithNormalizedKeyOrder);
} else if (data && (typeof data === 'object')) {
value = Object
.getOwnPropertyNames(data)
.sort(comparePropertyNames)
.reduce((obj, key) => {
obj[key] = getJsonDataWithNormalizedKeyOrder(data[key])
return obj;
}, {});
} else {
value = data;
}
return value;
}
const sampleList = [{
name: 'John',
value: 1,
}, {
value: 1,
name: 'John',
}, {
name: 'Carla',
value: 15,
}];
function hasDifferentValues(arr) {
// stringified first item reference.
const referenceItem = JSON.stringify(getJsonDataWithNormalizedKeyOrder(arr[0]));
// run `some` from a sub-array which excludes the original array's first item.
return arr.slice(1).some(item =>
referenceItem !== JSON.stringify(getJsonDataWithNormalizedKeyOrder(item))
);
}
console.log(
'hasDifferentValues(sampleList) ?',
hasDifferentValues(sampleList)
);
console.log(
'hasDifferentValues(sampleList.slice(0,2)) ?',
hasDifferentValues(sampleList.slice(0,2))
);
console.log(
'hasDifferentValues(sampleList.slice(0,1)) ?',
hasDifferentValues(sampleList.slice(0,1))
);
console.log(
'hasDifferentValues(sampleList.slice(1)) ?',
hasDifferentValues(sampleList.slice(1))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I'd check whether stringified object array includes stringified item by referencing to a copied array where I remove the latest item. I'll use Array.every() to compare if all the items match together and then return the opposite value.
However, this can be very heavy operation if an object array is very lengthy
const arrSame = [{name: 1}, {name: 1}, {name: 1}];
const arrDiff = [{name:1}, {name: 2}, {name: 2}];
const arrDiff2 = [{name:1}, {name: 1}, {name: 2}];
const hasDifferentValues = (arr) => !arr.every((item, i, ref) => JSON.stringify([...ref].shift()).includes(JSON.stringify(item)));
console.log(hasDifferentValues(arrSame));
console.log(hasDifferentValues(arrDiff));
console.log(hasDifferentValues(arrDiff2));
This is not exactly react specific, but to check for differences you can iterate through the array using every like so.
const fooArray = [{
name: 'John',
value: 1,
nest: {
isValid: [1, 2]
}
},
{
value: 1,
name: 'John',
nest: {
isValid: [1, 1]
}
}, {
name: 'John',
value: 1,
nest: {
isValid: [1, 1]
}
}
]
// check each member against the last, see if there is a diff
const isSame = (element, index, arr) => {
if (index > 0) {
// https://stackoverflow.com/questions/1068834/object-comparison-in-javascript
// return JSON.stringify(element) === JSON.stringify(arr[index - 1])
// alternatively, you can check to see if some of the values are different
// by stringifying and checking if either are permuations of each other
// this is probably not the ideal way, but I added it for the sake of a different solution
const currentObStr = JSON.stringify(element).split("").sort().join()
const prevObStr = JSON.stringify(arr[index - 1]).split("").sort().join()
return currentObStr === prevObStr
}
return true
}
const everyElementIsSame = fooArray.every(isSame)
console.log(everyElementIsSame)
Have an object like
{
A: { lists: 2, name: 'John' },
B: { lists: 2, name: 'Tom' },
C: { lists: 1, name: 'Harry' }
}
Here what I want to achieve is select the object key based on its index in the list of object and replace that key with a new input value.
Lets say index id 1 and new key value is T then the key B will be replaced with T and the new object will return as
{
A: { lists: 2, name: 'John' },
T: { lists: 2, name: 'Tom' },
C: { lists: 1, name: 'Harry' }
}
Any help would be great. I have gone through other questions but the answers are not helpful for me.
You could use Array.prototype.reduce() method. Get key-value pairs array using Object.entries() method. Then traverse the array using reduce and change the required key. At last, return the resultant object.
const change = (array, index, key) => {
return Object.entries(array).reduce((prev, [x, y], i) => {
const p = prev;
if (i === index) p[key] = { ...y };
else p[x] = { ...y };
return p;
}, {});
};
const data = {
A: { lists: 2, name: 'John' },
B: { lists: 2, name: 'Tom' },
C: { lists: 1, name: 'Harry' },
};
const ret = change(data, 1, 'T');
console.log(ret);
I have problem making a new array of object. I want to transform this
[{
a: 1,
b: true
},{
a: 2,
b: false
}]
to
[{
a_1: 1
},{
a_2: 2
}]
I tried map
const result = a.map((o, i) => {
let row = []
i = ++i
row = {
[`a_${i}`]: o.a,
[`b_${i}`]: b.a
}
return row
})
but it returned this
[
{
"a_1": 1,
"b_1": true
},
{
"a_2": 2,
"b_2": false
}
]
How do I get this
[
{
"a_1": 1,
},{
"b_1": true
},{
"a_2": 2,
},
{
"b_2": false
}
]
I can flatten it but the property key has dynamic index, imagine it's not small size like this.
You can use map and Object.entries and flat
let arr = [{ a: 1, b: true }, { a: 2, b: false}]
const result = arr.map((o, i) => {
return Object.entries(o).map(([key, value]) => ({
[key + '_' + (i + 1)]: value
}))
}).flat()
console.log(result)
Also you can use Array.flatMap
let arr = [{ a: 1, b: true }, { a: 2, b: false}]
const result = arr.flatMap((o, i) => {
return Object.entries(o).map(([key, value]) => ({
[key + '_' + (i + 1)]: value
}))
})
console.log(result)
And you can use reduce:
let arr = [{a: 1,b: true},{a: 2,b: false}];
let brr= arr.reduce((acc,e, i)=>{
Object.entries(e).map(([key,value])=>{
acc.push({[`${key}_${i+1}`]:value})
});
return acc
},[])
console.log(brr)
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);
};