基于递归的深层键结构

时间:2018-04-02 12:22:37

标签: javascript recursion lodash

我一直在使用lodash一段时间,我真的很喜欢_.set_.get方法。

我正在尝试解决一个问题,以获得最终值为字符串的深层键路径,但是当我对它太愚蠢时。花3个小时就找不到完美的解决方案:

const myObject = {
  a: 'myObject.a',
  b: {
    ba: 'myObject.b.ba',
    bb: ['myObject.b.bb[0]'],
  },
  c: [
    { ca: 'myObject.c[0].ca' },
  ],
};

所以我有myObject(在现实生活中更加嵌套)我希望获得值的路径,但只有最后的路径。

该方法看起来像getDeepPaths(myObject),在这种情况下会返回:['myObject.a', 'myObject.b.ba', 'myObject.b.bb[0]', 'myObject.c[0].ca' ]

之前有人解决过这样的问题吗?

2 个答案:

答案 0 :(得分:2)

递归实际上并不那么难。以下是解决此问题的方法:



const myObject = {
  a: 'myObject.a',
  b: {
    ba: 'myObject.b.ba',
    bb: ['myObject.b.bb[0]'],
  },
  c: [
    { ca: 'myObject.c[0].ca' },
  ],
};


var stringLeaves = function(path, obj) {

  if (typeof obj === 'string') {
    return [path]
  }

  return Object.keys(obj)
          .filter(k => obj.hasOwnProperty(k))
          .map(k => stringLeaves(path + '.' + k, obj[k]))
          .reduce((a,x) => a.concat(x), []); // this line flattens the array
};

console.log(stringLeaves('myObject', myObject));




工作由stringLeaves功能完成。在这个功能中:

  • 如果作为参数传入的obj是一个字符串,则只返回当前路径。
  • 否则我们假设对象是一个数组或一个通用对象,在这种情况下我们迭代它的属性:
    • 对于每个属性,通过传递调整后的路径(当前路径+新属性名称)和驻留在该特定键上的对象/值来递归调用stringLeaves

该函数的约定是它返回所有可能匹配的数组。这就是原因:

  • 对于标量字符串值我返回一个数组(以保持一致)
  • 我有.reduce((a,x) => a.concat(x), []);行:将数组数组转换为一个数组,该数组包含原始数组中的所有值。

请注意,该函数无法推断出您的对象被称为myObject,因此我将该名称作为初始路径传递。

答案 1 :(得分:0)

我将提供一个更通用的解决方案,它不使用lodash或其他外部依赖项

const traverse = function* (node, path = [])
{
  if (Object (node) === node)
    for (const [ key, value ] of Object.entries (node))
      yield* traverse (value, [ ...path, key ])
  else
    yield [ path, node ]
}

我们可以使用for循环轻松地逐步执行数据。请注意,生成器为原始对象中的每个值生成path - value对。 所有原语值都包含在输出中,而不仅仅是字符串

// add a non-string value for demo
const myObject = {
  ...
  d: 1
};

for (const [ path, value ] of traverse (myObject)) {
  console.log ('path', path)
  console.log ('value', value)
  console.log ('---')
}

// path [ 'a' ]
// value myObject.a
// ---
// path [ 'b', 'ba' ]
// value myObject.b.ba
// ---
// path [ 'b', 'bb', '0' ]
// value myObject.b.bb[0]
// ---
// path [ 'c', '0', 'ca' ]
// value myObject.c[0].ca
// ---
// path [ 'd' ]
// value 1
// ---

如果我们愿意,我们可以使用Array.from

收集所有对
Array.from (traverse (myObject))
// [ [ [ 'a' ], 'myObject.a' ]
// , [ [ 'b', 'ba' ], 'myObject.b.ba' ]
// , [ [ 'b', 'bb', '0' ], 'myObject.b.bb[0]' ]
// , [ [ 'c', '0', 'ca' ], 'myObject.c[0].ca' ]
// , [ [ 'd' ], 1 ]
// ]

您可能已经注意到,我将path保留为数组,而不是将其设为. - 分隔的字符串。没有必要把它变成一个字符串,只是为了以后再分开它。

const lookup = (obj, [ key, ...path ]) =>
  obj && key
    ? lookup (obj [key], path)
    : obj

for (const [ path, value ] of traverse (myObject)) {
  console.log ('path', path)
  console.log ('value', value)
  console.log ('lookup', lookup (myObject, path))
  console.log ('---')
}

// path [ 'a' ]
// value myObject.a
// lookup myObject.a
// ---
// path [ 'b', 'ba' ]
// value myObject.b.ba
// lookup myObject.b.ba
// ---
// path [ 'b', 'bb', '0' ]
// value myObject.b.bb[0]
// lookup myObject.b.bb[0]
// ---
// path [ 'c', '0', 'ca' ]
// value myObject.c[0].ca
// lookup myObject.c[0].ca
// ---
// path [ 'd' ]
// value 1
// lookup 1
// ---