0

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

{
    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 但这仅返回直接子属性。

4

3 回答 3

2

你可以使用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(".")))


或者你可以填充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如果可能,它返回一个值,否则返回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

于 2019-09-03T21:06:36.593 回答
1

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

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

于 2019-09-03T21:22:59.010 回答
1

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

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

于 2019-09-03T21:37:59.530 回答