Tengo un objeto anidado y mi objetivo es obtener la ruta hasta que un par clave-valor coincida en una matriz.
Mi implementación actual implementa esto mediante cadenas y concatenando las cadenas con el punto (".").
Sin embargo, en lugar de eso, me gustaría agregar todos los resultados intermedios a la matriz y presionarlos. Pero de alguna manera esto no funciona.
Código con datos de ejemplo
const data = [ { parentId: "1111", name: "Audi", children : [ { parentId: "2222", name: "Benz", children : [ { parentId: "3333", name: "VW", children : [ ] } ] } ] } ] const pathTo = (array, target) => { var result; array.some(({ parentId, name, children = [] }) => { if (parentId === target) { return result = JSON.stringify({"parentId" : parentId, "name" : name}); } var temp = pathTo(children, target) if (temp) { return result = JSON.stringify({"parentId" : parentId, "name" : name}) + "." + temp; } }); return result; }; console.log(pathTo(data, "3333"))Resultado actual
{"parentId":"1111","name":"Audi"}.{"parentId":"2222","name":"Benz"}.{"parentId":"3333","name":"VW"}=> El Camino concatenado con una cadena. Pero me gustaría :
Resultado Esperado
[ "{"parentId":"1111","name":"Audi"}", "{"parentId":"2222","name":"Benz"}"{"parentId":"3333","name":"VW"}"]=> una matriz con todos los elementos en orden posterior.
Podría devolver una matriz o undefined .
const data = [{ parentId: "1111", name: "Audi", children : [{ parentId: "2222", name: "Benz", children : [{ parentId: "3333", name: "VW", children : [] }] }] }], pathTo = (array, target) => { let result; array.some(({ parentId, name, children = [] }) => { if (parentId === target) { result = [{ parentId: parentId, name: name }]; return true; } const temp = pathTo(children, target) if (temp) { result = [{ parentId: parentId, name: name }, ...temp]; return true; } }); return result; }; console.log(pathTo(data, "3333")) .as-console-wrapper { max-height: 100% !important; top: 0; }Prefiero dividir problemas como este en pedazos y hacer que al menos algunos sean más genéricos en el proceso.
function * traversePaths (xs, path = []) { for (let x of xs) { const newPath = [...path, x] yield newPath yield * traversePaths (x .children || [], newPath) } } const deepFindPath = (pred) => (xs) => { for (let path of traversePaths (xs)) { if (pred (path [path.length - 1])) {return path} } } const pathByParentId = (target) => deepFindPath (({parentId}) => parentId == target) const data = [{parentId: "1111", name: "Audi", children : [{parentId: "2222", name: "Benz", children : [{parentId: "3333", name: "VW", children : []}]}]}] // stringified to avoid SO's `/**id:4**/` - `/**ref:4**/,` notation console .log (JSON .stringify ( pathByParentId ('3333') (data) , null, 4)) .as-console-wrapper {max-height: 100% !important; top: 0} Aquí, traversePaths es una función generadora que toma cualquier matriz cuyos elementos tengan propiedades children (opcionales y recursivas) y la atraviesa (preordenar) produciendo una matriz de nodos de objetos/subobjetos para cada ruta en la matriz.
deepFindPath devuelve la ruta al primer nodo que coincide con el predicado proporcionado.
Estas dos funciones son genéricas. Luego, para resolver su problema, escribimos el pathByParentId simple, que simplemente acepta una identificación de destino y pasa a deepFindPath una función que prueba si la propiedad parentId del nodo coincide con esa identificación de destino. Devolverá la ruta al primer nodo coincidente, o undefined si nada coincide.
Su salida solicitada coincidió con la anterior, excepto que no mencionó children nodos secundarios. Prefiero este retorno, solo referencias simples a los nodos existentes. Pero si realmente no quieres a los niños, entonces puedes hacer:
const pathByParentId = (target) => ( data, res = deepFindPath (({parentId}) => parentId == target) (data) ) => res && res .map (({children, ...rest}) => rest) Si bien podríamos hacer esto en la función transversal, haría que la función fuera menos genérica y probablemente menos útil. Pero si quisiera hacerlo, podría mantener la versión original de pathByParentId y reemplazar traversePaths con esto:
function * traversePaths (xs, path = []) { for (let {children, ...rest} of xs) { const newPath = [...path, rest] yield newPath yield * traversePaths (children || [], newPath) } } Esto es menos flexible que la versión anterior, pero aún enumera genéricamente las rutas a cualquier matriz cuyos elementos tengan propiedades children (opcionales y recursivas). Los elementos de esas listas son objetos nuevos, similares al original pero sin las propiedades children .
Si está de acuerdo con el uso de una dependencia, lo haría en su lugar. Aquí hay una solución usando object-scan .
.as-console-wrapper {max-height: 100% !important; top: 0} <script type="module"> import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan@18.1.2/lib/index.min.js'; const data = [{ parentId: '1111', name: 'Audi', children: [{ parentId: '2222', name: 'Benz', children: [{ parentId: '3333', name: 'VW', children: [] }] }] }]; const find = (obj, v) => objectScan(['**'], { abort: true, filterFn: ({ value }) => value === v, rtn: ({ parents }) => parents .filter((p) => !Array.isArray(p)) .reverse() .map(({ parentId, name }) => ({ parentId, name })) })(obj); console.log(find(data, '3333')); /* => [ { parentId: '1111', name: 'Audi' }, { parentId: '2222', name: 'Benz' }, { parentId: '3333', name: 'VW' } ] */ </script>Descargo de responsabilidad: soy el autor de object-scan