Tengo una función que me da la ruta al primer hallazgo de un objeto anidado donde la clave y el valor coinciden.
function getPath(obj, givenKey, givenValue) { for(var key in obj) { if(obj[key] && typeof obj[key] === "object") { var result = getPath(obj[key], givenValue, givenKey); if(result) { result.unshift(key) return result; } } else if(obj[key] === givenValue && key === givenKey ) { return [key]; } } }Data de muestra
var myObj = [ { "name": "needle", "children": [ { "name": "group2", "children": [ { "name": "item0" }] }] }, { "name": "item1" }, { "name": "needleGroup", "children": [ { "name": "needleNestedGroup", "children": [ { "name": "item3" }, { "name": "needleNestedDeeperGroup", "children": [ { "name": "needle" }] }] }] }];Rendimiento esperado
getPath(myObj, "name", "needle"): [0, "name"] ["2","children","0","children","1","children","0","name"]Sin embargo, ahora tengo un objeto que contiene estos valores-clave varias veces, por lo que tengo varias coincidencias.
¿Cómo puedo ponerlos todos en una matriz? Mi función actual simplemente se detiene después de encontrar la primera coincidencia. El hecho de que sea recursivo me complica mucho las cosas.
Escribiría esto encima de una función findAllPaths más genérica que acepta un predicado y encuentra las rutas de todos los nodos en el objeto que coinciden con ese predicado. Con eso, findPathsByName es tan simple como (target) => findAllPaths (({name}) => name == target) .
A su vez, construyo findAllPaths en pathEntries , variantes de las cuales uso todo el tiempo . Esta función convierte un objeto en una matriz de pares ruta/valor. Algunas versiones solo generan los nodos hoja. Este lo genera para todos los nodos, incluida la raíz (con una ruta vacía). La idea básica de esta función es convertir algo como:
{a: 'foo', b: {c: ['bar', 'baz'], f: 'qux'}}dentro de esto:
[ [[], {a: 'foo', b: {c: ['bar', 'baz'], f: 'qux'}}], [['a'], 'foo'], [['b'], {c: ['bar', 'baz'], f: 'qux'}], [['b', 'c'], ['bar', 'baz']], [['b', 'c', 0], 'bar'], [['b', 'c', 1], 'baz'], [['b', 'f'], 'qux'] ]donde el primer elemento de cada subarreglo es una ruta y el segundo una referencia al valor en esa ruta.
Esto es lo que podría parecer:
const pathEntries = (obj) => [ [[], obj], ...Object (obj) === obj ? Object .entries (obj) .flatMap ( ([k, x]) => pathEntries (x) .map ( ([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v] ) ) : [] ] const findAllPaths = (predicate) => (o) => [...pathEntries (o)] .filter (([p, v]) => predicate (v, p)) .map (([p]) => p) const findPathsByName = (target) => findAllPaths (({name}) => name == target) const myObj = [{name: "needle", children: [{name: "group2", children: [{name: "item0"}]}]}, {name: "item1"}, {name: "needleGroup", children: [{name: "needleNestedGroup", children: [{name: "item3"}, {name: "needleNestedDeeperGroup", children: [{name: "needle"}]}]}]}] console .log (findPathsByName ('needle') (myObj)) .as-console-wrapper {max-height: 100% !important; top: 0}La pregunta solicitaba valores de cadena para los índices de matriz. Prefiero los valores enteros como se hace aquí, pero simplificas un poco la función:
- ([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v] + ([p, v]) => [[k, ... p], v]En lugar de devolver el valor, puede enviarlo a una matriz y seguir iterando.
Al final de la función, return la matriz.
function getPath(obj, givenKey, givenValue) { let matches = []; for (var key in obj) { if (obj[key] && typeof obj[key] === "object") { var result = getPath(obj[key], givenValue, givenKey); if (result) { result.unshift(key) matches.push(...result); } } else if (obj[key] === givenValue && key === givenKey) { matches.push(key); } } return matches; }