Frapper un mur avec celui-ci, je pensais le poster ici au cas où une âme bienveillante en rencontrerait un semblable. J'ai des données qui ressemblent à ceci:
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss2' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
Je ne sais pas, au moment de l'exécution, quelle sera la profondeur de la hiérarchie, c'est-à-dire combien de niveaux d'objets auront un tableau d'enfants. J'ai quelque peu simplifié l'exemple, il va falloir que je compare les propriétés de la valeur à un tableau de termes de recherche. Supposons pour le moment que je corresponde à value.includes('Hit')
.
J'ai besoin d'une fonction qui retourne un nouveau tableau, telle que:
Aucun objet ne correspondant à aucun enfant, ni à aucune correspondance dans la hiérarchie des enfants, ne doit pas exister dans l'objet de sortie
Chaque objet avec un descendant contenant un objet correspondant doit rester
Tous les descendants des objets correspondants doivent rester
Je considère qu'un "objet correspondant" est un objet avec une propriété value
qui contient la chaîne Hit
dans ce cas, et inversement.
La sortie devrait ressembler à ceci:
const expected = [
{
value: 'Miss1',
children: [
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
]
}
];
Un grand merci à tous ceux qui ont pris le temps de lire jusque-là, publieront ma solution si j'arrive le premier.
Utiliser .filter()
et effectuer un appel récursif comme je l’ai décrit dans le commentaire ci-dessus est fondamentalement ce dont vous avez besoin. Il vous suffit de mettre à jour chaque propriété .children
avec le résultat de l'appel récursif avant de retourner.
La valeur de retour est simplement le .length
de la collection résultante .children
; ainsi, s'il en existe au moins un, l'objet est conservé.
var res = input.filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
const input = [
{
value: 'Miss1',
children: [
{ value: 'Miss2' },
{ value: 'Hit1', children: [ { value: 'Miss3' } ] }
]
},
{
value: 'Miss4',
children: [
{ value: 'Miss5' },
{ value: 'Miss6', children: [ { value: 'Hit2' } ] }
]
},
{
value: 'Miss7',
children: [
{ value: 'Miss8' },
{ value: 'Miss9', children: [ { value: 'Miss10' } ] }
]
},
{
value: 'Hit3',
children: [
{ value: 'Miss11' },
{ value: 'Miss12', children: [ { value: 'Miss13' } ] }
]
},
{
value: 'Miss14',
children: [
{ value: 'Hit4' },
{ value: 'Miss15', children: [ { value: 'Miss16' } ] }
]
},
];
var res = input.filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
console.log(JSON.stringify(res, null, 2))
Notez que .includes()
sur String est ES7, il faudra donc peut-être le corriger pour les navigateurs hérités. Vous pouvez utiliser le .indexOf("Hit") != -1
traditionnel à sa place.
Pour ne pas modifier l'original, créez une fonction de carte qui copie un objet et utilisez-la avant le filtre.
function copy(o) {
return Object.assign({}, o)
}
var res = input.map(copy).filter(function f(o) {
if (o.value.includes("Hit")) return true
if (o.children) {
return (o.children = o.children.map(copy).filter(f)).length
}
})
Pour vraiment compresser le code, vous pouvez faire ceci:
var res = input.filter(function f(o) {
return o.value.includes("Hit") ||
o.children && (o.children = o.children.filter(f)).length
})
Bien que cela devienne un peu difficile à lire.
Voici une fonction qui fera ce que vous cherchez. Essentiellement, il testera chaque élément de la variable arr
pour rechercher une correspondance, puis appellera récursivement le filtre sur sa children
. De plus, Object.assign
est utilisé pour que l'objet sous-jacent ne soit pas modifié.
function filter(arr, term) {
var matches = [];
if (!Array.isArray(arr)) return matches;
arr.forEach(function(i) {
if (i.value.includes(term)) {
matches.Push(i);
} else {
let childResults = filter(i.children, term);
if (childResults.length)
matches.Push(Object.assign({}, i, { children: childResults }));
}
})
return matches;
}
Je pense que ce sera une solution récursive. En voici une que j'ai essayée.
function find(obj, key) {
if (obj.value && obj.value.indexOf(key) > -1){
return true;
}
if (obj.children && obj.children.length > 0){
return obj.children.reduce(function(obj1, obj2){
return find(obj1, key) || find(obj2, key);
}, {});
}
return false;
}
var output = input.filter(function(obj){
return find(obj, 'Hit');
});
console.log('Result', output);
Sinon, vous pouvez utiliser la méthode _.filterDeep
à partir de deepdash extension pour lodash
:
// We will need 2 passes, first - to collect needed nodes with 'Hit' value:
var foundHit = _.filterDeep(input,
function(value) {
if (value.value && value.value.includes('Hit')) return true;
},{ condense: false, // keep empty slots in array to preserve correct paths
leafsOnly: false }
);
// second pass - to add missed fields both for found 'Hit' nodes and their parents.
var filtrate = _.filterDeep(input, function(value,key,path,depth,parent,parentKey,parentPath) {
if (_.get(foundHit, path) !== undefined ||
_.get(foundHit, parentPath) !== undefined) {
return true;
}
});
Voici un test complet pour votre cas