使用JavaScript查找给定值的完整对象路径

时间:2018-11-29 16:17:20

标签: javascript

我有一个对象数组,其中包含项目(仅具有name属性)和组(具有children属性,它们可能包含项目或其他组),我需要获取完整的路径needle的值,因此在这种情况下为myObj[2]["children"][0]["children"][1]["children"][0],而且我仅限于相当老的JS版本ECMA 262(我在Photoshop中使用它)

var myObj = [
{
    "name": "group1",
    "children": [
    {
        "name": "group2",
        "children": [
        {
            "name": "item0"
        }]
    }]
},
{
    "name": "item1"
},
{
    "name": "needleGroup",
    "children": [
    {
        "name": "needleNestedGroup",
        "children": [
        {
            "name": "item3"
        },
        {
            "name": "needleNestedDeeperGroup",
            "children": [
            {
                "name": "needle"
            }]
        }]
    }]
}];

我的第一个想法是将对象转换为一个或多个数组,以便于处理,因此我的对象变成了

[
    [
        [
            "item0"
        ]
    ],
    "item1", 
    [
        [
            "item3", 
            [
                "needle"
            ]
        ]
    ]
];

但是..就是这样。我无法找出仅追踪所需索引的方法。您能指出正确的方向吗?

3 个答案:

答案 0 :(得分:1)

使用递归函数查找所需的项目。一旦找到函数,它将返回一个数组。递归的每一步将unshift此步骤的对象键:

function find(obj, item) {
    for(var key in obj) {                                   // for each key in obj (obj is either an object or an array)
        if(obj[key] && typeof obj[key] === "object") {      // if the current property (value of obj[key]) is also an object/array
            var result = find(obj[key], item);              // try finding item in that object
            if(result) {                                    // if we find it
                result.unshift(key);                        // we shift the current key to the path array (result will be an array of keys)
                return result;                              // and we return it to our caller
            }
        } else if(obj[key] === item) {                      // otherwise (if obj[key] is not an object or array) we check if it is the item we're looking for
            return [key];                                   // if it is then we return the path array (this is where the path array get constructed)
        }
    }
}

此函数的输出将是指向item的一组键。您可以轻松地将其转换为问题中的格式:

function findFormatted(obj, item) {
    var path = find(obj, item);                             // first let's get the path array to item (if it exists)
    if(path == null) {                                      // if it doesn't exist
        return "";                                          // return something to signal its inexistance
    }
    return 'myObj["' + path.join('"]["') + '"]';            // otherwise format the path array into a string and return it
}

示例:

function find(obj, item) {
    for(var key in obj) {
        if(obj[key] && typeof obj[key] === "object") {
            var result = find(obj[key], item);
            if(result) {
                result.unshift(key);
                return result;
            }
        } else if(obj[key] === item) {
            return [key];
        }
    }
}

function findFormatted(obj, item) {
    var path = find(obj, item);
    if(path == null) {
        return "";
    }
    return 'myObj["' + path.join('"]["') + '"]';
}

var myObj = [{"name":"group1","children":[{"name":"group2","children":[{"name":"item0"}]}]},{"name":"item1"},{"name":"needleGroup","children":[{"name":"needleNestedGroup","children":[{"name":"item3"},{"name":"needleNestedDeeperGroup","children":[{"name":"needle"}]}]}]}];

console.log("find(myObj, \"needle\"):          " + JSON.stringify(find(myObj, "needle")));
console.log("findFormatted(myObj, \"needle\"): " + findFormatted(myObj, "needle"));

注意:数组的索引也设置为字符串格式,但这不会造成问题,因为someArray["2"]等效于someArray[2]

答案 1 :(得分:0)

假设您的数据集中有嵌套且重复的对象模式,以下解决方案将为您解决问题。

const nodePath = { value: [] };

function findTreeNodeAndPath(
  targetNodeKey,
  targetNodeValue,
  nodeChildrenKey,
  tree
) {
  if (tree[targetNodeKey] === targetNodeValue) {
    nodePath.value.push(tree);
    return;
  }

  if (tree[nodeChildrenKey].length > 0) {
    tree[nodeChildrenKey].forEach(children => {
      if (nodePath.value.length < 1) {
        findTreeNodeAndPath(
          targetNodeKey,
          targetNodeValue,
          nodeChildrenKey,
          children
        );
      }
    });
  } else if (tree[nodeChildrenKey].length === 0) {
    return;
  }

  if (nodePath.value.length > 0) {
    nodePath.value.push(tree);
  }
}

const exampleData = {
  name: "Root",
  children: [
    {
      name: "A2",
      children: [
        {
          name: "AC1",
          children: [
            {
              name: "ACE1",
              children: []
            }
          ]
        },
        {
          name: "AC2",
          children: [
            {
              name: "ACF1",
              children: []
            },
            {
              name: "ACF2",
              children: [
                {
                  name: "ACFG1",
                  children: []
                }
              ]
            },
            {
              name: "ACF3",
              children: [
                {
                  name: "ACFH1",
                  children: []
                }
              ]
            }
          ]
        }
      ]
    }
  ]
};

findTreeNodeAndPath("name", "ACFG1", "children", exampleData);
console.log(nodePath.value)

代码的递归部分将在当前节点的子代上迭代。这里的现有策略取决于具有至少一个元素的nodePath.value,这表明它找到了目标节点。稍后,它将跳过其余节点,并且会中断递归。

nodePath.value变量将为您提供节点到根的路径。

答案 2 :(得分:0)

我创建了一些您可能会使用的东西。下面的代码返回到您要查找的键,值,对象的路径数组。

请参见代码段和示例,以了解您可以做什么。

要使其工作,您必须在要作为数组或对象的element和element中传递要查找的键和/或值。

它是用较新的JS标准编写的,但是将其编译为较旧的标准应该不是问题。

function findKeyValuePairsPath(keyToFind, valueToFind, element) {
  if ((keyToFind === undefined || keyToFind === null) &&
    (valueToFind === undefined || valueToFind === null)) {
    console.error('You have to pass key and/or value to find in element!');
    return [];
  }

  const parsedElement = JSON.parse(JSON.stringify(element));
  const paths = [];

  if (this.isObject(parsedElement) || this.isArray(parsedElement)) {
    checkObjOrArr(parsedElement, keyToFind, valueToFind, 'baseElement', paths);
  } else {
    console.error('Element must be an Object or Array type!', parsedElement);
  }

  console.warn('Main element', parsedElement);
  return paths;
}

function isObject(elem) {
  return elem && typeof elem === 'object' && elem.constructor === Object;
}

function isArray(elem) {
  return Array.isArray(elem);
}

function checkObj(obj, keyToFind, valueToFind, path, paths) {
  Object.entries(obj).forEach(([key, value]) => {
    if (!keyToFind && valueToFind === value) {
      // we are looking for value only
      paths.push(`${path}.${key}`);
    } else if (!valueToFind && keyToFind === key) {
      // we are looking for key only
      paths.push(path);
    } else if (key === keyToFind && value === valueToFind) {
      // we ale looking for key: value pair
      paths.push(path);
    }

    checkObjOrArr(value, keyToFind, valueToFind, `${path}.${key}`, paths);
  });
}

function checkArr(array, keyToFind, valueToFind, path, paths) {
  array.forEach((elem, i) => {
    if (!keyToFind && valueToFind === elem) {
      // we are looking for value only
      paths.push(`${path}[${i}]`);
    }
    checkObjOrArr(elem, keyToFind, valueToFind, `${path}[${i}]`, paths);
  });
}

function checkObjOrArr(elem, keyToFind, valueToFind, path, paths) {
  if (this.isObject(elem)) {
    checkObj(elem, keyToFind, valueToFind, path, paths);
  } else if (this.isArray(elem)) {
    checkArr(elem, keyToFind, valueToFind, path, paths);
  }
}

const example = [
  {
    exampleArr: ['lol', 'test', 'rotfl', 'yolo'],
    key: 'lol',
  },
  {
    exampleArr: [],
    key: 'lol',
  },
  {
    anotherKey: {
      nestedKey: {
        anotherArr: ['yolo'],
      },
      anotherNestedKey: 'yolo',
    },
    emptyKey: null,
    key: 'yolo',
  },
];

console.log(findKeyValuePairsPath('key', 'lol', example)); // ["baseElement[0]", "baseElement[1]"]
console.log(findKeyValuePairsPath(null, 'yolo', example)); // ["baseElement[0].exampleArr[3]", "baseElement[2].anotherKey.nestedKey.anotherArr[0]", "baseElement[2].anotherKey.anotherNestedKey", "baseElement[2].key"]
console.log(findKeyValuePairsPath('anotherNestedKey', null, example)); //["baseElement[2].anotherKey"]