检索对象中最深层属性的路径

Retrieve paths of deepest properties in an object

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

{
    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 但这只是 returns 直接子属性。

你可以用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))
    , []
    )
}

Is there a way to access any of those properties' value? Eg. "d.name.split.first" using the returned array at position 3?

我们可以编写一个查找函数,它接受一个对象 o 和一个点分隔的字符串 s,如果可能的话 returns 一个值,否则 returns undefined 如果 s 无法访问 -

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

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

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))

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

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) );

版画

Array ["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.select.month", "date.select.year"]