I need help with tree walking to find matches.
EG: TEXT_TO_MATCH exists at .a[0].b.c.[0].value
,
It should return all values which have the path:
.a[ANY_INDEX].b.c[ANY_INDEX].value
const pathToMatch = findText(DATA, 'A') // returns .a[0].b.c[0].value
findObjectInTree(DATA, pathToMatch) // returns all data which appear at .a[ANY_INDEX].b.c[ANY_INDEX].value
For example, my input data looks like this:
{
a:[
{ b:{ c:[{value:'A'}]}},
{ b:{ c:[{foo:1},{value:'B'}]}},
{ b:{ c:[{value:'C',
b: { c:
[ {value:'D'} ]
}
}]
}}
],
a1:[{ b:{ c:[{value:'AA'}]}}]
}
The result would be:
['A','B','C']
Why?
A,B,C ARE returned as they are on the same object path
.a[ANY_INDEX].b.c[ANY_INDEX].value
D are NOT returned because it is deeper in the path at
.a[ANY_INDEX].b.c[ANY_INDEX].b.c[ANY_INDEX].value
AA are NOT returned because it is on a different object key a1
.a1[ANY_INDEX].b.c[ANY_INDEX].value
The actual data my be larger and more nested- but a text match will always exist on the SAME path but at a different index of a ARRAY.
First get the path to the found string, second collect values with this path:
const obj = {
a: [
{ b: { c: [{ value: 'A' }] } },
{ b: { c: [{ foo: 1 }, { value: 'B' }] } },
{
b: {
c: [{
value: 'C',
b: {
c:
[{ value: 'D' }]
}
}]
}
}
],
a1: [
{ b: { c: [{ value: 'A1' }] } },
{ b: { c: [{ foo: 1 }, { value: 'B1' }] } },
{
b: {
c: [{
value: 'C1',
b: {
c:
[{ value: 'D1' }]
}
}]
}
}
],
};
const str = 'A';
// first find the value and its path from the root
const path = findPath(obj, str);
if (!path) {
console.error(new Error(`The string "${str}" not found`));
} else {
// walk in path and collect values
const collected = [];
collect(obj, path, collected);
console.log(collected);
}
function collect(obj, path, collected) {
const part = path.shift();
if (!path.length) {
part in obj && collected.push(obj[part]);
return;
}
if (part === 0) { // it's an array, iterate
for (const item of obj) {
collect(item, path.slice(), collected);
}
return;
}
part in obj && collect(obj[part], path, collected);
}
function findPath(obj, str, path = [], isArray) {
for (const [key, val] of Object.entries(obj)) {
const out = path.concat(isArray ? 0 : key);
const isChildArray = Array.isArray(val);
if (isChildArray || val.__proto__.constructor.name === 'Object') {
const found = findPath(val, str, out, isChildArray);
if (found) {
return found;
}
} else if (typeof val === 'string' && val.includes(str)) {
return out;
}
}
}