【问题标题】:Find path of string in object or array在对象或数组中查找字符串的路径
【发布时间】:2021-07-25 05:42:08
【问题描述】:

给定一个对象或数组,我希望能够确定路径是否存在。

给定 - 示例 1

const spath = "data/message";
const body = {
  data: {
    school: 'yaba',
    age: 'tolu',
    message: 'true'
  },
  time: 'UTC',
  class: 'Finals'
}

它应该返回 true,因为消息可以在 body.data.message 中找到,否则返回 false。

给定 - 示例 2

const spath = "data/message/details/lastGreeting";
const body = {
  data: {
    school: 'yaba',
    age: 'tolu',
    message: {
       content: 'now',
       details: {
          lastGreeting: true
       }
    }
  },
  time: 'UTC',
  class: 'Finals'
}

它应该返回 true,因为 lastGreeting 可以在 body.data.message.details.lastGreeting 中找到,否则返回 false。

另一种情况是当body由一个数组组成时

给定 - 示例 3

const spath = "data/area/NY";
const body = {
  data: {
    school: 'yaba',
    age: 'tolu',
    names : ['darious'],
    area: [{
       NY: true,
       BG: true
    ]]
    message: {
       content: 'now',
       details: {
          lastGreeting: true
       }
    }
  },
  time: 'UTC',
  class: 'Finals'
}

它应该返回 true,因为 NY 可以在 body.data.area[0].NY 中找到,否则返回 false。

这是我想出的解决方案

const findPathInObject = (data, path, n) => {
  console.log('entered')
  console.log(data, path)
  

  if(!data){
    return false
  }
  
  let spath = path.split('/');
  for(let i = 0; i<n; i++){
    
    let lastIndex = spath.length - 1;
    if(spath[i] in data && spath[i] === spath[lastIndex]){
      return true
    }
    
    const currentIndex = spath[i];
//     spath.splice(currentIndex, 1);
    return findPathInObject(data[spath[currentIndex]], spath[i+1], spath.length)
    
  }
  
  return false
}

console.log(findPathInObject(body, spath, 3))

【问题讨论】:

  • 你面临什么问题?
  • 由于 spath[i+1] 而改变了 lastIndex - 我想防止这种情况发生。 lastindex 应该始终是路径中的最后一项。还要为其中包含数组的对象做出规定
  • 你想要递归解决方案吗?
  • 是的,递归解决方案也可以工作

标签: javascript recursion


【解决方案1】:

这是一个使用object-scan的干净的迭代解决方案

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

const data1 = { data: { school: 'yaba', age: 'tolu', message: 'true' }, time: 'UTC', class: 'Finals' };
const data2 = { data: { school: 'yaba', age: 'tolu', message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' };
const data3 = { data: { school: 'yaba', age: 'tolu', names: ['darious'], area: [{ NY: true, BG: true }], message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' };

const path1 = 'data/message';
const path2 = 'data/message/details/lastGreeting';
const path3 = 'data/area/NY';

const exists = (data, n) => objectScan([n.replace(/\//g, '.')], {
  useArraySelector: false,
  rtn: 'bool',
  abort: true
})(data);

console.log(exists(data1, path1));
// => true

console.log(exists(data2, path2));
// => true

console.log(exists(data3, path3));
// => true
.as-console-wrapper {max-height: 100% !important; top: 0}
&lt;script src="https://bundle.run/object-scan@16.0.2"&gt;&lt;/script&gt;

免责声明:我是object-scan的作者

【讨论】:

    【解决方案2】:

    在一位同事的帮助下,我们最终想出了一些真正适合我们需求的简单易懂的东西。 yield 实现的答案解决了这个问题,但我们一直在寻找一些可以在代码库中阅读并易于理解的东西。我们希望能够检查对象中是否存在路径并获取值。

    所以我们添加了第三个参数returnValue - 默认情况下它总是返回值。如果我们不希望它这样做,我们可以将返回值的值设置为false,该函数将检查正在查找的路径是否存在,如果存在,它将返回true,否则返回@987654326 @

    这就是我们最终想到的

    const find = (path, data) => {
        if (Array.isArray(data)) {
            data = data[0];
        }
        for (const item in data) {
            if (item === path) {
                return data[item];
            }
        }
        return null;
    };
    
    const findPath = (fullPath, fullData, returnValue = true) => {
    
        const pathArray = fullPath.split('/');
    
        let findResult = fullData;
        for (const pathItem of pathArray) {
            findResult = find(pathItem, findResult);
            if (!findResult) {
                if (!returnValue) return false;
                return null;
            }
        }
    
        if (!returnValue) return true;
        return findResult;
    };
    
    
    
    const body = {
      name: 'mike',
      email: 1,
      data: {
        school: [
          {
            testing: 123
          }
        ]
      }
    }
    
    console.log(findPath('data/school/testing', body))
    

    【讨论】:

    • 请注意,这只检查数组的 first 元素。虽然findPath ('data/school/testing', {data: {school: [{testing: 123}]}}) 产生123findPath ('data/school/testing', {data: {school: [{noTesting: 345}, {testing: 123}]}}) 产生null,即使第二个元素中有testing。如果这是您的要求,那很好。但如果不是,让我建议您通过查看@Thankyou 的答案会学到很多东西。
    • 是的,它符合我们的要求。感谢您的反馈。为了学习的目的,我也可以改进它以适应它。请问@Thankyou是谁?
    • 用户Thankyou 写了accepted answer,并写了一些我在 StackOverflow 上看到的最鼓舞人心和信息量最大的答案。
    【解决方案3】:

    找到

    对于这个答案,我将提供一个tree,其中包含不同程度的对象和数组嵌套-

    const tree =
      { data:
          { school: "yaba", age: "tolu", message: "foo" }
      , classes:
          [ { name: "math" }, { name: "science" } ]
      , deep:
          [ { example:
                [ { nested: "hello" }
                , { nested: "world" }
                ]
            }
          ]
      }
    

    生成器非常适合此类问题。从一个通用的find 开始,它会为特定路径产生所有个可能的结果 -

    function find (data, path)
    { function* loop (t, [k, ...more])
      { if (t == null) return
        if (k == null) yield t
        else switch (t?.constructor)
        { case Object:
            yield *loop(t[k], more)
          break
          case Array:
            for (const v of t)
              yield *loop(v, [k, ...more])
            break
        }
      }
      return loop(data, path.split("/"))
    }
    
    Array.from(find(tree, "classes/name"))
    Array.from(find(tree, "deep/example/nested"))
    Array.from(find(tree, "x/y/z"))
    
    [ "math", "science" ]
    [ "hello", "world" ]
    []
    

    find1

    如果你想要一个返回一个(第一个)结果的函数,我们可以很容易地写成find1。这特别有效,因为生成器是可暂停/可取消的。找到第一个结果后,生成器将停止搜索其他结果 -

    function find1 (data, path)
    { for (const result of find(data, path))
        return result
    }
    
    find1(tree, "data/school")
    find1(tree, "classes")
    find1(tree, "classes/name")
    find1(tree, "deep/example/nested")
    find1(tree, "x/y/z")
    
    "yaba"
    [ { name: "math" }, { name: "science" } ]
    "math"
    "hello"
    undefined
    

    存在

    如果您想检查特定路径是否存在,我们可以将exists 写为find1 的简单特化 -

    const exists = (data, path) =>
      find1(data, path) !== undefined
    
    exists(tree, "data/school")
    exists(tree, "classes")
    exists(tree, "deep/example/nested")
    exists(tree, "x/y/z")
    
    true
    true
    true
    false
    

    演示

    展开下面的sn-p,在自己的浏览器中验证结果-

    function find (data, path)
    { function* loop (t, [k, ...more])
      { if (t == null) return
        if (k == null) yield t
        else switch (t?.constructor)
        { case Object:
            yield *loop(t[k], more)
          break
          case Array:
            for (const v of t)
              yield *loop(v, [k, ...more])
            break
        }
      }
      return loop(data, path.split("/"))
    }
    
    function find1 (data, path)
    { for (const result of find(data, path))
        return result
    }
    
    const tree =
      { data:
          { school: "yaba", age: "tolu", message: "foo" }
      , classes:
          [ { name: "math" }, { name: "science" } ]
      , deep:
          [ { example:
                [ { nested: "hello" }
                , { nested: "world" }
                ]
            }
          ]
      }
    
    console.log("find1")
    console.log(find1(tree, "data/school"))
    console.log(find1(tree, "classes"))
    console.log(find1(tree, "classes/name"))
    console.log(find1(tree, "deep/example/nested"))
    console.log(find1(tree, "x/y/z"))
    console.log("find")
    console.log(Array.from(find(tree, "classes/name")))
    console.log(Array.from(find(tree, "deep/example/nested")))
    console.log(Array.from(find(tree, "x/y/z")))

    【讨论】:

      【解决方案4】:

      您可以检查此解决方案。还将检查对象数组。

      const body = {
          data: {
              school: 'yaba',
              age: 'tolu',
              message: {
                  content: 'now',
                  details: {
                      lastGreeting: true,
                  },
              },
              area: [
                  {
                      NY: true,
                      BG: true,
                  },
              ],
          },
          time: 'UTC',
          class: 'Finals',
      };
      const spath1 = 'data/message';
      const spath2 = 'data/message/details/lastGreeting';
      const spath3 = 'data/area/NY';
      const spath4 = 'data/area/NY/Test';
      
      console.log(`${spath1}: `, isPathExists(body, spath1.split('/'), 0));
      console.log(`${spath2}: `, isPathExists(body, spath2.split('/'), 0));
      console.log(`${spath3}: `, isPathExists(body, spath3.split('/'), 0));
      console.log(`${spath4}: `, isPathExists(body, spath4.split('/'), 0));
      
      function isPathExists(data, pathArr, i) {
          const key = pathArr[i];
      
          if (Array.isArray(data)) {
              for (let value of data) {
                  if (isObject(value)) return isPathExists(value, pathArr, i);
              }
          } else if (data.hasOwnProperty(key)) {
              if (key === pathArr[pathArr.length - 1]) return true;
              return isPathExists(data[key], pathArr, i + 1);
          } else return false;
      
          return true;
      }
      function isObject(a) {
          return !!a && a.constructor === Object;
      }

      【讨论】:

        【解决方案5】:

        您可以提前进行一些检查并检查路径是否为空字符串,然后以true 退出。

        通过拥有一个数组,您可以通过省略索引来检查数组的元素与实际路径,从而提前退出。

        对于键的最终检查,您可以检查它是否存在,并返回带有其余路径的递归调用的结果,如果该键不在对象中,则返回 false

        const
            findPathInObject = (data, path) => {
                if (!path) return true;
                if (!data || typeof data !== 'object') return false;
                if (Array.isArray(data)) return data.some(d => findPathInObject(d, path));
        
                const
                    spath = path.split('/'),
                    key = spath.shift();
        
                return key in data
                    ? findPathInObject(data[key], spath.join('/'))
                    : false;
            };
        
        console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', message: 'true' }, time: 'UTC', class: 'Finals' }, "data/message", 3)); // true
        
        console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' }, "data/message/details/lastGreeting", 3)); // true
        
        console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', names: ['darious'], area: [{ NY: true, BG: true }], message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' }, "data/area/NY", 3)); // true

        【讨论】:

        • 谢谢,当我尝试检查不存在的路径时,它会引发错误,例如使用此路径 - data/message/jdjfjdkf 会引发错误“TypeError: Cannot use 'in' operator to在 true 中搜索“jdjfjdkf”
        • 您也需要检查对象。请参阅编辑。
        • 谢谢 - 我认为您的解决方案也涵盖了数组部分
        【解决方案6】:

        严格来说,body.data.area[0].NY 不在body 的路径中,抱歉。 body.data.area 在路径中。对于没有body.data.area 作为数组的对象,这是一个解决方案。如果您想在数组中包含对象作为对象路径的一部分,则解决方案会更复杂

        const spath = "data/area/NY";
        const spath2 = "data/message/details/lastGreeting";
        const notPath = "data/message/details/firstGreeting";
        const body = {
          data: {
            school: 'yaba',
            age: 'tolu',
            names : ['darious'],
            area: {
               NY: true,
               BG: true
            },
            message: {
               content: 'now',
               details: {
                  lastGreeting: true
               }
            }
          },
          time: 'UTC',
          class: 'Finals'
        };
        
        console.log(`${spath} exists? ${ exists(body, spath) && `yep` || `nope`}`);
        console.log(`${spath2} exists? ${ exists(body, spath2) && `yep` || `nope`}`);
        console.log(`${notPath} exists? ${ exists(body, notPath) && `yep` || `nope`}`);
        
        function exists(obj, path) {
          const pathIterable = path.split("/");
          while (pathIterable.length) {
            const current = pathIterable.shift();
            // no path left and exists: true
            if (pathIterable.length < 1 && current in obj) { return true; }
            // up to now exists, path continues: recurse
            if (current in obj) { return exists(obj[current], pathIterable.join("/")); }
          }
          // no solution found: false
          return false;
        }

        【讨论】:

        • 谢谢,body.data.area[0].NY 实际上不在路径中。我可以处理这个。再次感谢
        猜你喜欢
        • 1970-01-01
        • 2019-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-19
        • 1970-01-01
        相关资源
        最近更新 更多