【问题标题】:Retrieve paths of deepest properties in an object检索对象中最深属性的路径
【发布时间】:2020-01-06 18:30:16
【问题描述】:

我正在尝试找到一种方法来动态创建一个数组,该数组包含嵌套对象中最深属性的路径。例如,如果我的对象如下:

{
    userName: [],
    email: [],
    name: {
        fullName: [],
        split: {
            first: [],
            last: []
        }
    },
    date: {
        input: {
            split: {
                month: [],
                year: []
            },
            full: []
        },
        select: {
            month: [],
            year: []
        }
    }
};

我需要一个数组来包含以下内容:

["userName", "email", "name.fullName", "name.split.first",...]

是否有任何内置或外部库可以自动执行此操作?我试图在父对象上使用 Object.keys 但这只会返回直接子属性。

【问题讨论】:

    标签: javascript javascript-objects nested-object


    【解决方案1】:

    您可以为此使用Array.prototype.flatMap -

    const d =
      {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}
    
    const main = (o = {}, path = []) =>
      Array.isArray(o) || Object(o) !== o
        ? [ path ]
        : Object
            .entries(o)
            .flatMap(([ k, v ]) => main(v, [...path, k ]))
            
    console.log(main(d))

    输出

    [ [ "userName" ]
    , [ "email" ]
    , [ "name", "fullName" ]
    , [ "name" ,"split", "first" ]
    , [ "name", "split", "last" ]
    , ...
    ]
    

    如果您希望路径为 "a.b.c" 而不是 [ "a", "b", "c" ],请使用 .mapArray.prototype.join -

    const d =
      {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}
    
    const main = (o = {}, path = []) =>
      Array.isArray(o) || Object(o) !== o
        ? [ path ]
        : Object
            .entries(o)
            .flatMap(([ k, v ]) => main(v, [...path, k ]))
            
    console.log(main(d).map(path => path.join(".")))

    输出

    [
      "userName",
      "email",
      "name.fullName",
      "name.split.first",
      "name.split.last",
      "date.input.split.month",
      "date.input.split.year",
      "date.input.full",
      "date.select.month",
      "date.select.year"
    ]
    

    如果您不想依赖Array.prototype.flatMap,因为您的环境不支持它,您可以结合使用Array.prototype.reduceArray.prototype.concat -

    const d =
      {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}
    
    const main = (o = {}, path = []) =>
      Array.isArray(o) || Object(o) !== o
        ? [ path ]
        : Object
            .entries(o)
            .reduce // <-- manual flatMap
              ( (r, [ k, v ]) =>
                  r.concat(main(v, [...path, k ]))
              , []
              )
            
    console.log(main(d).map(path => path.join(".")))

    或者你可以 polyfill Array.prototype.flatMap -

    Array.prototype.flatMap = function (f, context)
    { return this.reduce
        ( (r, x, i, a) => r.concat(f.call(context, r, x, i, a))
        , []
        )
    }
    

    有没有办法访问这些属性的值?例如。 "d.name.split.first" 在位置 3 使用返回的数组?

    我们可以编写一个查找函数,它接受一个对象 o 和一个点分隔的字符串 s,如果可能,它会返回一个值,否则如果 s 不可达,则返回 undefined -

    const d =
      {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}
      
    const lookup = (o = {}, s = "") =>
      s
        .split(".")
        .reduce
          ( (r, x) =>
              r == null ? undefined : r[x]
          , o
          )
     
    console.log(lookup(d, "name.split"))
    // { first: [], last: [] }
    
    console.log(lookup(d, "name.split.first"))
    // []
    
    console.log(lookup(d, "name.split.zzz"))
    // undefined
    
    console.log(lookup(d, "zzz"))
    // undefined

    【讨论】:

    • 我不会仅仅因为大约 5% 的人无法获得支持,就将支持描述为“有限”。也许它甚至没有在浏览器中使用?为 Web 构建 JS 的 JS 开发人员熟悉(或将不得不熟悉)必须对事物进行 polyfill。是否应该针对每个 JS 问题进行讨论?
    • 只是注意到它,所以用户不会盲目使用它。
    • @NoamSuissa,我想我明白你在问什么。我更新了答案。 Lmk 如果这就是你要找的东西^^
    • @llermaly 我还有其他一些答案可以改进和改进此答案中的技术,每个都处理任何嵌套级别的对象和数组。请参阅example onetwothree。如果您有任何问题,我很乐意为您提供帮助。
    【解决方案2】:

    有很多方法可以做到这一点。最简单的只是一个递归来测试你是否有一个 Object 并遍历键来跟踪每一步的路径。

    var myObj = {
        userName: [],
        email: [],
        name: {
            fullName: [],
            split: {
                first: [],
                last: []
            }
        },
        date: {
            input: {
                split: {
                    month: [],
                    year: []
                },
                full: []
            },
            select: {
                month: [],
                year: []
            }
        }
    }
    
    function setPath(a, b) {
      return a.length ? a + '.' + b : b
    }
    
    function getAllPaths(obj, paths, currentPath) {
      if (paths===undefined) paths = []
      if (currentPath===undefined) currentPath = ''
      Object.entries(obj).forEach( function (entry) {
        const updatedPath = setPath(currentPath, entry[0])
        if (entry[1] instanceof Object && !Array.isArray(entry[1])) { 
          getAllPaths(entry[1], paths, updatedPath)
        } else {
          paths.push(updatedPath)
        }
      })
      return paths
    }
    
    
    console.log(getAllPaths(myObj))

    用箭头函数和默认值编写

    var myObj = {
        userName: [],
        email: [],
        name: {
            fullName: [],
            split: {
                first: [],
                last: []
            }
        },
        date: {
            input: {
                split: {
                    month: [],
                    year: []
                },
                full: []
            },
            select: {
                month: [],
                year: []
            }
        }
    }
    
    const setPath = (a, b) => a.length ? a + '.' + b : b
    
    const getAllPaths = (obj, paths=[], currentPath='') => {
      Object.entries(obj).forEach( ([key, value]) => {
        const updatedPath = setPath(currentPath, key)
        if (value instanceof Object && !Array.isArray(value)) { 
          getAllPaths(value, paths, updatedPath)
        } else {
          paths.push(updatedPath)
        }
      })
      return paths
    }
    
    
    console.log(getAllPaths(myObj))

    【讨论】:

      【解决方案3】:

      来吧。数组和对象基本上是一回事。绝对不需要未定义的检查。

      data = { ... };
      
      function paths_list( value, result=[], path=[] )
      {
          for ( keydx in value )
          {
              if ( value[keydx] instanceof Object )
              {
                  path.push( keydx );
                  result.push( path.join(".") );
                  paths_list( value[keydx], result, path );
                  path.pop();
              }
          }
      
          return result;
      }
      
      console.log( paths_list(data) );
      

      打印

      数组 ["userName", "email", "name", "name.fullName", "name.split", "name.split.first", "name.split.last", "date", " date.input"、"date.input.split"、"date.input.split.month"、"date.input.split.year"、"date.input.full"、"date.select"、"date.input. select.month", "date.select.year"]

      【讨论】:

      • 出色的解决方案? ?
      • @masoudmanson 谈到递归,我喜欢让事情保持简单,否则我的大脑会开始融化 ;-) 但是 ty.
      • @Holli 我不太明白“加油”的原因,但非常感谢您的解决方案
      • 这不是针对您,而是针对我的回答者;-)
      猜你喜欢
      • 2013-09-24
      • 2021-10-16
      • 1970-01-01
      • 2021-11-19
      • 2013-06-16
      • 2010-12-30
      • 2016-01-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多