Suchen Sie nach Schlüssel tief in einem verschachtelten Array

74

Angenommen, ich habe ein Objekt:

[
    {
        'title': "some title"
        'channel_id':'123we'
        'options': [
                    {
                'channel_id':'abc'
                'image':'http://asdasd.com/all-inclusive-block-img.jpg'
                'title':'All-Inclusive'
                'options':[
                    {
                        'channel_id':'dsa2'
                        'title':'Some Recommends'
                        'options':[
                            {
                                'image':'http://www.asdasd.com'                                 'title':'Sandals'
                                'id':'1'
                                'content':{
                                     ...

Ich möchte das eine Objekt finden, bei dem die ID 1 ist. Gibt es eine Funktion für so etwas? Ich könnte die _.filterMethode von Underscore verwenden, aber ich müsste oben beginnen und nach unten filtern.

Harry
quelle

Antworten:

87

Rekursion ist dein Freund. Ich habe die Funktion aktualisiert, um Eigenschaftsarrays zu berücksichtigen:

function getObject(theObject) {
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i]);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            console.log(prop + ': ' + theObject[prop]);
            if(prop == 'id') {
                if(theObject[prop] == 1) {
                    return theObject;
                }
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop]);
                if (result) {
                    break;
                }
            } 
        }
    }
    return result;
}

jsFiddle aktualisiert: http://jsfiddle.net/FM3qu/7/

Zach
quelle
1
Aber warte, sieh dir meinen Code an, Optionen könnten viele Objekte haben [{}, {}]. Wie würde das funktionieren?
Harry
Dort. Ich habe bereits erwähnt, dass es aktualisiert werden muss, um Arrays zu berücksichtigen.
Zach
Dies funktioniert nicht, wenn Sie mehr als ein Objekt in Optionen haben
Harry
1
Fest. Ich habe die Ergebnisvariable und die else-Logik hinzugefügt. Die aktualisierte jsFiddle zeigt, dass es funktioniert.
Zach
1
Zack Ich habe deinen Code ein wenig geändert, er funktioniert jetzt gut. Vielen Dank! Es scheint, dass ich momentan nicht auf jsfiddle zugreifen kann. Ich werde meine Änderungen teilen, sobald es funktioniert
Harry
24

Was für mich funktioniert hat, war dieser faule Ansatz, nicht algorithmisch faul;)

if( JSON.stringify(object_name).indexOf("key_name") > -1 ) {
    console.log("Key Found");
}
else{
    console.log("Key not Found");
}
abhinav1602
quelle
Was halten Sie davon, dies stattdessen zu verwenden: if( JSON.stringify(object_name).indexOf('{"key_name":') > -1 ) {einschließlich der linken geschweiften Klammer, doppelter Anführungszeichen und des Doppelpunkts, den JSON.stringify hinzufügt? Ich denke, es könnte eine nette Überprüfung sein, um sicherzustellen, dass der Schlüsselname tatsächlich ein Schlüssel ist und nicht irgendwie im "Wert" -Teil.
Edwardsmarkf
2
Potenziell gut schlägt jedoch Tests fehl, die diesen Schlüsselnamen irgendwo in einem Wert haben. Gibt auch nicht das Objekt zurück, das die ID enthält
Ranga
@Ranga ... stimmte zu, diese Lösung funktioniert nur, wenn Sie nur überprüfen müssen, ob der Schlüssel in einem Objekt vorhanden ist, vorausgesetzt, der Schlüsselname ist in einem der Werte nicht vorhanden. Wir können den Code jedoch ändern '' 'um den Schlüsselnamen in die indexOf-Funktion aufzunehmen, wenn wir sicherstellen müssen, dass er nur in Schlüsseln vorhanden ist. (Auch hier sollte der Schlüsselname mit' '' nicht in den Werten vorhanden sein.
abhinav1602
1
Ich liebe es! JSON.stringify(object).includes(text)
Marcelo Pereira Rodrigues
19

Wenn Sie das erste Element erhalten möchten, dessen ID 1 ist, während das Objekt durchsucht wird, können Sie diese Funktion verwenden:

function customFilter(object){
    if(object.hasOwnProperty('id') && object["id"] == 1)
        return object;

    for(var i=0; i<Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object"){
            var o = customFilter(object[Object.keys(object)[i]]);
            if(o != null)
                return o;
        }
    }

    return null;
}

Wenn Sie alle Elemente erhalten möchten, deren ID 1 ist, dann (alle Elemente, deren ID 1 ist, werden im Ergebnis gespeichert, wie Sie sehen):

function customFilter(object, result){
    if(object.hasOwnProperty('id') && object.id == 1)
        result.push(object);

    for(var i=0; i<Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object"){
            customFilter(object[Object.keys(object)[i]], result);
        }
    }
}
Haitaka
quelle
1
Ihre Lösung ist besser als die von @regularmike vorgeschlagene, da ich sie durch Hinzufügen einer Schlüssel- und Wertsuche anpassen könnte. Es funktioniert bei Ihnen und nicht bei ihm. jsfiddle.net/76fp54xt Arbeitsbeispiel
Iulian Pinzaru
7

Eine andere (etwas alberne) Option besteht darin, die natürlich rekursive Natur von auszunutzen JSON.stringifyund ihm eine Ersetzungsfunktion zu übergeben, die während des Stringifizierungsprozesses für jedes verschachtelte Objekt ausgeführt wird:

const input = [{
  'title': "some title",
  'channel_id': '123we',
  'options': [{
    'channel_id': 'abc',
    'image': 'http://asdasd.com/all-inclusive-block-img.jpg',
    'title': 'All-Inclusive',
    'options': [{
      'channel_id': 'dsa2',
      'title': 'Some Recommends',
      'options': [{
        'image': 'http://www.asdasd.com',
        'title': 'Sandals',
        'id': '1',
        'content': {}
      }]
    }]
  }]
}];

console.log(findNestedObj(input, 'id', '1'));

function findNestedObj(entireObj, keyToFind, valToFind) {
  let foundObj;
  JSON.stringify(input, (_, nestedValue) => {
    if (nestedValue && nestedValue[keyToFind] === valToFind) {
      foundObj = nestedValue;
    }
    return nestedValue;
  });
  return foundObj;
};

Bestimmte Leistung
quelle
Es ist wirklich klug
Claudiu
1
Ich mag deine Idee :) Ich denke nur, dass es besser aussehen wird, wenn die Ersetzungsfunktion rekursiv unterbrochen wird, wenn wir bereits ein Ergebnis gefunden haben. Zum Beispiel können wir verwenden throw. jsfiddle.net/w4vs36hm
Max Martynov
6

Ich habe diese Seite durch googeln nach ähnlichen Funktionen gefunden. Basierend auf der Arbeit von Zach und regulärmike habe ich eine andere Version erstellt, die meinen Bedürfnissen entspricht.
Übrigens, Teriffic Work Zah und Regularmike! Ich werde den Code hier posten:

function findObjects(obj, targetProp, targetValue, finalResults) {

  function getObject(theObject) {
    let result = null;
    if (theObject instanceof Array) {
      for (let i = 0; i < theObject.length; i++) {
        getObject(theObject[i]);
      }
    }
    else {
      for (let prop in theObject) {
        if(theObject.hasOwnProperty(prop)){
          console.log(prop + ': ' + theObject[prop]);
          if (prop === targetProp) {
            console.log('--found id');
            if (theObject[prop] === targetValue) {
              console.log('----found porop', prop, ', ', theObject[prop]);
              finalResults.push(theObject);
            }
          }
          if (theObject[prop] instanceof Object || theObject[prop] instanceof Array){
            getObject(theObject[prop]);
          }
        }
      }
    }
  }

  getObject(obj);

}

Was sie tut , ist es , jedes Objekt innerhalb von findet objmit Eigenschaftsnamen und Wertanpassung targetPropund targetValueund es wird auf den Push - finalResultsArray. Und hier ist die jsfiddle zum Herumspielen: https://jsfiddle.net/alexQch/5u6q2ybc/

Alex Quan
quelle
2
Dies könnte weiter verbessert werden, indem returndas finalResultsArray verwendet wird, anstatt eine andere Variable als Eingabe zum Schreiben zu akzeptieren
RozzA
4

Ich habe zu diesem Zweck eine Bibliothek erstellt: https://github.com/dominik791/obj-traverse

Sie können folgende findFirst()Methode verwenden:

var foundObject = findFirst(rootObject, 'options', { 'id': '1' });

Und jetzt foundObjectspeichert die Variable einen Verweis auf das gesuchte Objekt.

dominik791
quelle
4

Verbesserte @ haitaka-Antwort mit Schlüssel und Prädikat

function  deepSearch (object, key, predicate) {
    if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object

    for (let i = 0; i < Object.keys(object).length; i++) {
      let value = object[Object.keys(object)[i]];
      if (typeof value === "object" && value != null) {
        let o = deepSearch(object[Object.keys(object)[i]], key, predicate)
        if (o != null) return o
      }
    }
    return null
}

Dies kann also wie folgt aufgerufen werden:

var result = deepSearch(myObject, 'id', (k, v) => v === 1);

oder

var result = deepSearch(myObject, 'title', (k, v) => v === 'Some Recommends');

Hier ist die Demo: http://jsfiddle.net/a21dx6c0/

BEARBEITET

Auf die gleiche Weise können Sie mehr als ein Objekt finden

function deepSearchItems(object, key, predicate) {
        let ret = [];
        if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) {
            ret = [...ret, object];
        }
        if (Object.keys(object).length) {
            for (let i = 0; i < Object.keys(object).length; i++) {
                let value = object[Object.keys(object)[i]];
                if (typeof value === "object" && value != null) {
                    let o = this.deepSearchItems(object[Object.keys(object)[i]], key, predicate);
                    if (o != null && o instanceof Array) {
                        ret = [...ret, ...o];
                    }
                }
            }
        }
        return ret;
    }
Iulian Pinzaru
quelle
2
Hinweis: typeof null === 'object'Wenn also Werte im Objekt null sind, stürzt die Funktion ab, wenn versucht wird, sie zu wiederholen. Sie sollten daher auch die Richtigkeit überprüfen, bevor Sie eine Rekursion durchführen.
Rafi
@ Rafi danke für die Erwähnung, bearbeitet die Antwort.
Iulian Pinzaru
2

@Iulian Pinzarus Antwort war fast genau das, was ich brauchte, aber es funktioniert nicht, wenn Ihre Objekte Nullwerte haben. Diese Version behebt das.

function  deepSearch (object, key, predicate) {
  if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object

  for (let i = 0; i < Object.keys(object).length; i++) {
    const nextObject = object[Object.keys(object)[i]];
    if (nextObject && typeof nextObject === "object") {
      let o = deepSearch(nextObject, key, predicate)
      if (o != null) return o
    }
  }
  return null
}
Villarrealisiert
quelle
2

Ich würde versuchen, das Rad nicht neu zu erfinden. Wir verwenden den Objekt-Scan für alle unsere Datenverarbeitungsanforderungen. Es ist konzeptionell sehr einfach, erlaubt aber viele coole Sachen. So würden Sie Ihre spezifische Frage lösen

const objectScan = require('object-scan');

const find = (id, input) => objectScan(['**'], {
  abort: true,
  rtn: 'value',
  filterFn: ({ value }) => value.id === id
})(input);

const data = [{
  title: 'some title',
  channel_id: '123we',
  options: [{
    channel_id: 'abc',
    image: 'http://asdasd.com/all-inclusive-block-img.jpg',
    title: 'All-Inclusive',
    options: [{
      channel_id: 'dsa2',
      title: 'Some Recommends',
      options: [{
        image: 'http://www.asdasd.com',
        title: 'Sandals',
        id: '1',
        content: {}
      }]
    }]
  }]
}];

console.log(find('1', data));
// => { image: 'http://www.asdasd.com', title: 'Sandals', id: '1', content: {} }
Vincent
quelle
Wo wird die Eingabe verwendet?
user3808307
1
@ user3808307 Korrigierte
vincent
1

Verbesserte Antwort zur Berücksichtigung von Zirkelverweisen innerhalb von Objekten. Es zeigt auch den Weg an, den es genommen hat, um dorthin zu gelangen.

In diesem Beispiel suche ich nach einem Iframe, von dem ich weiß, dass er sich irgendwo in einem globalen Objekt befindet:

const objDone = []
var i = 2
function getObject(theObject, k) {
    if (i < 1 || objDone.indexOf(theObject) > -1) return
    objDone.push(theObject)
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i], i);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            if(prop == 'iframe' && theObject[prop]) {
                i--;
                console.log('iframe', theObject[prop])
                return theObject[prop]
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop], prop);
                if (result) {
                    break;
                }
            } 
        }
    }
    if (result) console.info(k)
    return result;
}

Ausführen der folgenden: getObject(reader, 'reader') gab die folgende Ausgabe und das iframe-Element am Ende:

iframe // (The Dom Element)
_views
views
manager
rendition
book
reader

HINWEIS: Der Pfad ist in umgekehrter Reihenfolge reader.book.rendition.manager.views._views.iframe

Gerardlamo
quelle
1

Ich möchte eine Änderung der Antwort von Zach / RegularMike vorschlagen (habe aber nicht den "Ruf", einen Kommentar abgeben zu können!). Ich fand diese Lösung eine sehr nützliche Basis, litt aber in meiner Anwendung, weil sie, wenn Zeichenfolgen in Arrays vorhanden sind, die Funktion für jedes Zeichen in der Zeichenfolge rekursiv aufruft (was dazu führte, dass IE11- und Edge-Browser mit Fehlern "Nicht genügend Stapelspeicher" fehlschlugen ). Meine einfache Optimierung bestand darin, den gleichen Test wie im rekursiven Aufruf der "Objekt" -Klausel zu dem in der "Array" -Klausel verwendeten hinzuzufügen:

if (arrayElem instanceof Object || arrayElem instanceof Array) {

Daher lautet mein vollständiger Code (der jetzt nach allen Instanzen eines bestimmten Schlüssels sucht, also etwas anders als die ursprüngliche Anforderung):

// Get all instances of specified property deep within supplied object
function getPropsInObject(theObject, targetProp) {
    var result = [];
    if (theObject instanceof Array) {
        for (var i = 0; i < theObject.length; i++) {
            var arrayElem = theObject[i];
            if (arrayElem instanceof Object || arrayElem instanceof Array) {
                result = result.concat(getPropsInObject(arrayElem, targetProp));
            }
        }
    } else {
        for (var prop in theObject) {
            var objProp = theObject[prop];
            if (prop == targetProp) {
                return theObject[prop];
            }
            if (objProp instanceof Object || objProp instanceof Array) {
                result = result.concat(getPropsInObject(objProp, targetProp));
            }
        }
    }
    return result;
}
Richard T.
quelle
1

Vor einiger Zeit habe ich eine kleine Bibliothek erstellt find-and, die auf npm verfügbar ist , um mit verschachtelten Objekten auf lodash-Weise zu arbeiten. Da ist derreturnFound Funktion, die das gefundene Objekt zurückgibt, oder ein Objektarray, wenn mehr als ein Objekt gefunden wurde.

Z.B,

const findAnd = require('find-and');

const a = [
  {
    'title': "some title",
    'channel_id':'123we',
    'options': [
      {
        'channel_id':'abc',
        'image':'http://asdasd.com/all-inclusive-block-img.jpg',
        'title':'All-Inclusive',
        'options':[
          {
            'channel_id':'dsa2',
            'title':'Some Recommends',
            'options':[
              {
                'image':'http://www.asdasd.com',
                'title':'Sandals',
                'id':'1',
                'content':{},
              },
            ],
          },
        ],
      },
    ],
  },
];

findAnd.returnFound(a, {id: '1'});

kehrt zurück

{
  'image':'http://www.asdasd.com',
  'title':'Sandals',
  'id':'1',
  'content':{},
}
Arfeo
quelle
0

Verwenden Sie einfach die rekursive Funktion.
Siehe Beispiel unten:

const data = [
  {
    title: 'some title',
    channel_id: '123we',
    options: [
      {
        channel_id: 'abc',
        image: 'http://asdasd.com/all-inclusive-block-img.jpg',
        title: 'All-Inclusive',
        options: [
          {
            channel_id: 'dsa2',
            title: 'Some Recommends',
            options: [
              {
                image: 'http://www.asdasd.com',
                title: 'Sandals',
                id: '1',
                content: {},
              }
            ]
          }
        ]
      }
    ]
  }
]

function _find(collection, key, value) {
  for (const o of collection) {
    for (const [k, v] of Object.entries(o)) {
      if (k === key && v === value) {
        return o
      }
      if (Array.isArray(v)) {
        const _o = _find(v, key, value)
        if (_o) {
          return _o
        }
      }
    }
  }
}

console.log(_find(data, 'channel_id', 'dsa2'))

Thiago Lagden
quelle
0

Eine andere rekursive Lösung, die für Arrays / Listen und Objekte oder eine Mischung aus beiden funktioniert:

function deepSearchByKey(object, originalKey, matches = []) {

    if(object != null) {
        if(Array.isArray(object)) {
            for(let arrayItem of object) {
                deepSearchByKey(arrayItem, originalKey, matches);
            }
        } else if(typeof object == 'object') {

            for(let key of Object.keys(object)) {
                if(key == originalKey) {
                    matches.push(object);
                } else {
                    deepSearchByKey(object[key], originalKey, matches);
                }

            }

        }
    }


    return matches;
}

Verwendung:

let result = deepSearchByKey(arrayOrObject, 'key'); // returns an array with the objects containing the key
Ali AlNoaimi
quelle
0

    fucntion getPath(obj, path, index = 0) {
        const nestedKeys = path.split('.')
        const selectedKey = nestedKeys[index]

        if (index === nestedKeys.length - 1) {
            return obj[selectedKey]
        }

        if (!obj.hasOwnProperty(selectedKey)) {
            return {}
        }

        const nextObj = obj[selectedKey]

        return Utils.hasPath(nextObj, path, index + 1)
    }

Gern geschehen Von: Gorillaz

Nicolas de Lima
quelle
-1

Mit diesem Code können Sie alle Objekte in einem JSON abrufen, dessen Schlüssel benutzerdefiniert ist.

function main(obj = {}, property){
 const views = [];

 function traverse(o) {
    for (var i in o) {
      if(i === property) views.push(o[i]);
      if (!!o[i] && typeof(o[i])=="object") {
        console.log(i, o[i]);
        traverse(o[i]);
      } else {
        console.log(i, o[i]);
      }
    }
    }

  traverse(obj);
  return views;

}



Hier ist ein Beispiel:

const obj = {
    id: 'id at level 1',
    level2: {
      id: 'id at level 2',
      level3: {
        id: 'id at level 3',
        level4: {
          level5: {
            id: 'id at level 5'
          }
       }
    }
  },
  text: ''
}

main(obj, 'id');

LuisP
quelle
-16

Wenn Sie bereits Unterstrich verwenden, verwenden Sie _.find ()

_.find(yourList, function (item) {
    return item.id === 1;
});
Rhodesjason
quelle
Es würde funktionieren, wenn sich das Objekt in der ersten Ebene des Objekts befindet, aber nicht, es ist verschachtelt mit in
Harry
2
Dang. Ich schaute auf mein Handy und die Art und Weise, wie das Objekt verpackt war, ließ es so aussehen, als wäre es auf der ersten Ebene. Ich fand es zu einfach.
Rhodesjason