【问题标题】:How to get the path from javascript object from key and value如何从键和值获取javascript对象的路径
【发布时间】:2014-10-13 18:08:11
【问题描述】:

我有一个 javascript 对象的宽度深度。

我需要知道对象中此键的确切路径,例如:“obj1.obj2.data1”

我已经知道key是data1,value是123。

我的 javascript 对象看起来像这样

{
    obj1: {
        obj2: {
            data1: 213,
            data2: "1231",
            obj3: {
                data: "milf"
            }
        }
    },
    obj4: {
        description: "toto"
    }
}

我怎样才能做到这一点?

这是一个 jsfiddle:http://jsfiddle.net/3hvav8xf/8/ 我正在尝试实现 getPath。

【问题讨论】:

  • 这样做有什么意义?
  • 它是用来实现一个变更日志的,如果我们能这样做的话,今天的做法会更容易
  • 是mongodb更新吗?
  • @TamilVendhan 不,不是,它是用于将 json 更改日志从一个版本制作到另一个版本
  • @Prinzhorn 我正在开发一个现有系统,不需要重新实现所有逻辑。我无法实现一个新库,这需要几个月的测试。请不要为我提供更多替代解决方案,小提琴只是在这里展示我想要实现的目标

标签: javascript object


【解决方案1】:

我认为递归函数可以帮助你(更新版本,检查价值)

function path(c, name, v, currentPath, t){
    var currentPath = currentPath || "root";

    for(var i in c){
      if(i == name && c[i] == v){
        t = currentPath;
      }
      else if(typeof c[i] == "object"){
        return path(c[i], name, v, currentPath + "." + i);
      }
    }

    return t + "." + name;
};

console.log(path({1: 2, s: 5, 2: {3: {2: {s: 1, p: 2}}}}, "s", 1));

【讨论】:

  • 您不是在检查值,我们需要验证该值是否是我们要查找的值,出于特定原因,我们可以有许多具有相同名称的键,但它们可以t 具有相同的值
  • 它似乎在我的调试器中工作正常,而小提琴失败:jsfiddle.net/3hvav8xf/9,我必须接受答案,因为你完全回答了这个问题
  • 为什么t是那个函数的参数?
  • @Cold_Class 这是因为 t 设置为 c 运行的当前路径
  • 这不起作用,除非键值对是对象中的第一个。 console.log(path({1: 2, s: 5, 2: {3: {2: {s: 1, p: 2}}}}, "p", 1));会给你 undefined.p
【解决方案2】:

以下查找任何级别的嵌套对象中的路径。还有数组。 它返回找到的所有路径,如果您有同名的键,这是您想要的。

我喜欢这种方法,因为它适用于开箱即用的lodash 方法getset

function findPathsToKey(options) {
  let results = [];

  (function findKey({
    key,
    obj,
    pathToKey,
  }) {
    const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
    if (obj.hasOwnProperty(key)) {
      results.push(`${oldPath}${key}`);
      return;
    }

    if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
      for (const k in obj) {
        if (obj.hasOwnProperty(k)) {
          if (Array.isArray(obj[k])) {
            for (let j = 0; j < obj[k].length; j++) {
              findKey({
                obj: obj[k][j],
                key,
                pathToKey: `${oldPath}${k}[${j}]`,
              });
            }
          }

          if (obj[k] !== null && typeof obj[k] === "object") {
            findKey({
              obj: obj[k],
              key,
              pathToKey: `${oldPath}${k}`,
            });
          }
        }
      }
    }
  })(options);

  return results;
}

findPathsToKey({ obj: objWithDuplicates, key: "d" })
// ["parentKey.arr[0].c.d", "parentKey.arr[1].c.d", "parentKey.arr[2].c.d"]

在这里试试 - https://jsfiddle.net/spuhb8v7/1/

如果您希望结果是单个键(第一次遇到),您可以将 results 更改为字符串,如果已定义,则返回带有它的函数。

【讨论】:

    【解决方案3】:

    我最终得到了以下函数,它适用于嵌套对象/数组:

    function findPath (obj, name, val, currentPath) {
      currentPath = currentPath || ''
    
      let matchingPath
    
      if (!obj || typeof obj !== 'object') return
    
      if (obj[name] === val) return `${currentPath}['${name}']`
    
      for (const key of Object.keys(obj)) {
        if (key === name && obj[key] === val) {
          matchingPath = currentPath
        } else {
          matchingPath = findPath(obj[key], name, val, `${currentPath}['${key}']`)
        }
    
        if (matchingPath) break
      }
    
      return matchingPath
    }
    
    const treeData = [{
      id: 1,
      children: [{
        id: 2
      }]
    }, {
      id: 3,
      children: [{
        id: 4,
        children: [{
          id: 5
        }]
      }]
    }]
    
    console.log(findPath (treeData, 'id', 5))

    【讨论】:

      【解决方案4】:

      给你!

      function getPath(obj, value, path) {
      
          if(typeof obj !== 'object') {
              return;
          }
      
          for(var key in obj) {
              if(obj.hasOwnProperty(key)) {
                  console.log(key);
                  var t = path;
                  var v = obj[key];
                  if(!path) {
                      path = key;
                  }
                  else {
                      path = path + '.' + key;
                  }
                  if(v === value) {
                      return path;
                  }
                  else if(typeof v !== 'object'){
                      path = t;
                  }
                  var res = getPath(v, value, path);
                  if(res) {
                      return res;
                  } 
              }
          }
      
      }
      
      getPath(yourObject, valueYouWantToFindPath);
      

      如果找到则重新分配路径,否则返回未定义。 我只用对象测试过,比较非常严格(即:使用===)。

      更新: 以 key 作为参数的更新版本。

      function getPath(obj, key, value, path) {
      
          if(typeof obj !== 'object') {
              return;
          }
      
          for(var k in obj) {
              if(obj.hasOwnProperty(k)) {
                  console.log(k);
                  var t = path;
                  var v = obj[k];
                  if(!path) {
                      path = k;
                  }
                  else {
                      path = path + '.' + k;
                  }
                  if(v === value) {
                      if(key === k) {
                          return path;
                      }
                      else {
                          path = t;
                      }
                  }
                  else if(typeof v !== 'object'){
                      path = t;
                  }
                  var res = getPath(v, key, value, path);
                  if(res) {
                      return res;
                  } 
              }
          }
      
      }
      
      getPath(yourObject, key, valueYouWantToFindPath);
      

      【讨论】:

      • 谢谢,我已经在我的小提琴中尝试了你的 fn,你在这个函数中缺少一个参数,我们知道原始对象,我们知道我们正在寻找的密钥,我们也知道她的价值。我们要找的钥匙在哪里?我正在尝试编辑这个以获得我需要的东西
      • 以上代码没有考虑key。它只需要 obj 和您想要找到的值。让我看看我是否可以修改代码以使用密钥。
      • 非常感谢,我也必须接受这一点,因为它有效并且您回答正确。仍然不在小提琴中:jsfiddle.net/3hvav8xf/10,@farhatmihalko 答案有点短顺便说一句:]
      • 对我也不起作用,因为 path = k; 这会覆盖路径,因此在 for 循环的第二次迭代中你会得到一个错误的路径,因为路径应该仍然是空的。 - 我改变了它,这个对我有用:jsfiddle.net/pa5gcvvx
      • @Cold_Class 的答案对我很有效。其他 - 没有 =(
      【解决方案5】:

      JSON 对象可以在 JavaScript 中作为关联数组处理。

      因此,您可以在某些变量中循环并存储“父母”的索引。

      假设将整个对象存储在名为 obj 的变量中。

      for( var p1 in obj )

      {

         for( var p2 in obj[ p1 ] )
         {
             for( var p3 in obj[ p1 ][ p2 ] )
             {
                 // obj[ p1 ][ p2 ][ p3 ] is current node
                 // so for Your example it is obj.obj1.obj2.data1
             }
         }
      

      }

      希望回答对您有所帮助。

      【讨论】:

        【解决方案6】:

        我会按如下方式完成这项工作;

        Object.prototype.paths = function(root = [], result = {}) {
          var ok = Object.keys(this);
          return ok.reduce((res,key) => { var path = root.concat(key);
                                          typeof this[key] === "object" &&
                                                 this[key] !== null ? this[key].paths(path,res)
                                                                    : res[this[key]] == 0 || res[this[key]] ? res[this[key]].push(path)
                                                                                                            : res[this[key]] = [path];
                                          return res;
                                        },result);
        };
        
        var myObj = {
            obj1: {
                obj2: {
                    data1: 213,
                    data2: "1231",
                    obj3: {
                        data: "milf"
                    }
                }
            },
            obj4: {
                description: "toto",
                cougars: "Jodi",
                category: "milf"
            }
        },
        value = "milf",
        milfPath = myObj.paths()[value]; // the value can be set dynamically and if exists it's path will be listed.
        console.log(milfPath);

        几句警告:我们在玩 Object 原型时应该小心。我们的修改应该有描述符enumerable = false,否则它将在for in 循环中列出,例如jQuery 将不起作用。 (这就是 jQuery 的愚蠢之处,因为显然他们没有在他们的 for in 循环中进行 hasOwnProperty 检查)一些不错的读物是 herehere 所以我们必须添加这个 Object 方法和 Object.defineProperty() 来制作它enumerable = false;。但是为了简单起见并保持在问题的范围内,我没有在代码中包含该部分。

        【讨论】:

        • 那么如何从 for 循环中删除它?
        【解决方案7】:

        这是我编写的一个非常简短且相对容易理解的函数,用于检索对象上每个属性/字段的 JSON 路径(无论嵌套多深)。

        getPaths(object) 函数只获取您想要 JSON 路径的对象并返回一个路径数组。或者,如果您希望使用不同于标准 JSON 路径符号 $ 的符号表示初始对象,则可以调用 getPaths(object, path),每个 JSON 路径都将以指定路径开头。

        例如:getPaths({prop: "string"}, 'obj'); 将返回以下 JSON 路径:obj.prop,而不是 $.prop

        有关getPaths 返回的内容及其使用方式的更详细、深入的示例,请参见下文。

        object = {
          "firstName": "John",
          "lastName": "doe",
          "age": 26,
          "fakeData": true,
          "address": {
            "streetAddress": "fake street",
            "city": "fake city",
            "postalCode": "12345"
          },
          "phoneNumbers": [{
            "type": "iPhone",
            "number": "0123-4567-8888"
          }, {
            "type": "home",
            "number": "0123-4567-8910"
          }]
        };
        
        function getPaths(object, path = "$") {
          return Object.entries(object).flatMap(function(o, i) {
            if (typeof o[1] === "object" && !o[1].length) {
              return `${getPaths(o[1], path + '.' + o[0])}`.split(',');
            } else if (typeof o[1] === "object" && o[1].length) {
              return Object.entries(o[1]).flatMap((no, i) => getPaths(no[1], `${path}.${o[0]}[${i}]`));
            } else {
              return `${path}.${o[0]}`;
            }
          });
        }
        console.log(`%o`, getPaths(object));

        【讨论】:

          【解决方案8】:

          我真的很喜欢 Roland Jegorov 的回答,但我有一个非常复杂的对象需要搜索,而这个答案无法解释它。

          如果您处于像我这样的情况,您可能需要首先确保您没有循环引用(否则您将遇到无限搜索)。有几种方法可以做到这一点,但我不得不对我的对象进行字符串化以将其复制到其他窗口中,所以我最终使用了这个循环替换器:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value

          (在此处更新 - 我对 MDN 中的 getCircularReplacer 函数做了一个小改动,因此它不再遗漏函数引用,因为这是我正在寻找的!)

          (更新 3 - 我还想检查类的任何实例的方法,但我返回的只是“函数”太早了,所以我已经对其进行了调整以包含实例方法。我认为它终于可以按我的预期工作了! )

          const getCircularReplacer = () => {
            const seen = new WeakSet();
            return (key, value) => {
              if (typeof value === "function") {
                if (value?.prototype) {
                  if (seen.has(value.prototype)) {
                    return;
                  }
                  seen.add(value.prototype)
                  return value.prototype
                }
                return "function";
              }
              if (typeof value === "object" && value !== null) {
                if (seen.has(value)) {
                  return;
                }
                seen.add(value);
              }
              return value;
            };
          };
          
          const nonCyclicObject = JSON.parse(JSON.stringify(myComplexObject, getCircularReplacer()));
          

          然后我使用了这个修改版的罗兰回答:

          (更新 2:我必须确保在找到密钥后不返回,因为如果对象的第一级具有该密钥,它只会在仅调用一次函数后返回)

          function findPathsToKey(options) {
            let count = 0;
            let results = [];
          
            (function findKey({
              key,
              obj,
              pathToKey,
            }) {
              count += 1;
              if (obj === null) return;
              
              const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
              if (Object.hasOwnProperty.call(obj, key)) {
                results.push(`${oldPath}${key}`);
              }
              
              if (typeof obj === "object" && !Array.isArray(obj)) {
                for (const k in obj) {
                  if (Object.hasOwnProperty.call(obj, k)) {
                    if (Array.isArray(obj[k])) {
                      for (let j = 0; j < obj[k].length; j++) {
                        findKey({
                          obj: obj[k][j],
                          key,
                          pathToKey: `${oldPath}${k}[${j}]`,
                        });
                      }
                    }
          
                    if (typeof obj[k] === "object") {
                      findKey({
                        obj: obj[k],
                        key,
                        pathToKey: `${oldPath}${k}`,
                      });
                    }
                  }
                }
              }
            })(options);
          
            return { count, results };
          };
          

          计数只是为了解决一点问题并确保它实际上运行了我认为的键数量。希望这可以帮助任何其他寻找解决方案的人!

          【讨论】:

            【解决方案9】:

            我强烈建议您使用lodash 来解决这个问题。

            在他们的文档中this should help you out

            // using "_.where" callback shorthand
            _.find(characters, { 'age': 1 });
            // →  { 'name': 'pebbles', 'age': 1, 'blocked': false }
            

            【讨论】:

            • 这不会返回我需要的路径
            • 为什么需要路径?
            • 我需要保存原始商店位置的参考
            • 但是,我的意思是,你想用它做什么?如果是静态路径,你可以很容易地手动找到它,如果是动态的,我看不出保存它的意义。
            • 可以是动态的,我刚刚加了个jsfiddle
            猜你喜欢
            • 2014-05-24
            • 1970-01-01
            • 2021-11-27
            • 1970-01-01
            • 1970-01-01
            • 2018-09-12
            • 1970-01-01
            • 2021-05-31
            • 1970-01-01
            相关资源
            最近更新 更多