如何获取对象中嵌套属性的键路径?

时间:2016-11-25 00:52:59

标签: javascript recursion firebase firebase-realtime-database

我正在使用Firebase,他们的update函数允许使用以下语法进行多地点更新:

newData['users/abc/age'] = 23
newData['users/xyz/hair'] = 'blue'
firebase.database().ref().update(newData)

我正在开发一个实用程序函数,它将深层嵌套的对象展平为像newData这样的对象。这涉及递归迭代对象并在循环下降时跟踪键。

到目前为止,我有这个(现在只是将路径层添加到数组中。一旦算法正确,我将只使用.join('/')):

function getKeypaths(obj, paths = []) {

    let keys = Object.keys(obj)

    for (let i = 0; i < keys.length; i++) {
        let propName = keys[i]

        paths.push(propName)
        if (typeof obj[propName] === "object" && typeof obj[propName] !== null) {
            console.log("PUSHING PATH", propName, paths)
            getKeypaths(obj[propName], paths)
        } else {
            console.log("val", obj[propName], paths)

        }
    }
}


let obj = {
    a: {
        b: {
            c: {
                d: 1
            }
        },
        x: {
            y: {
                z: 2
            }
        }
    }
}

getKeypaths(obj)

当我运行它时,console.log输出如下所示:

PUSHING PATH a [ 'a' ]
PUSHING PATH b [ 'a', 'b' ]
PUSHING PATH c [ 'a', 'b', 'c' ]
val 1 [ 'a', 'b', 'c', 'd' ]
PUSHING PATH x [ 'a', 'b', 'c', 'd', 'x' ]
PUSHING PATH y [ 'a', 'b', 'c', 'd', 'x', 'y' ]
val 2 [ 'a', 'b', 'c', 'd', 'x', 'y', 'z' ]

如您所见,循环在开始挖掘下一个非对象值时保留前一个值的路径。输出的最后一行应为:val 2 [ 'a', 'x', 'y', 'z' ]

然后,当循环返回到先前级别时,我尝试清除path数组:

function getKeypaths(obj, paths = []) {
    let keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
        let propName = keys[i]
        if (propName) paths.push(propName)
        if (typeof obj[propName] === "object" && typeof obj[propName] !== null) {
            console.log("PUSHING PATH", propName, paths)
            getKeypaths(obj[propName], paths)
            paths.length = 0
        } else {
            console.log("val", obj[propName], paths)
        }
    }
}

getKeypaths(obj)

但这只会导致循环在第一个path之后忘记value之前的当前等级:

PUSHING PATH a [ 'a' ]
PUSHING PATH b [ 'a', 'b' ]
PUSHING PATH c [ 'a', 'b', 'c' ]
val 1 [ 'a', 'b', 'c', 'd' ]
PUSHING PATH x [ 'x' ]
PUSHING PATH y [ 'x', 'y' ]
val 2 [ 'x', 'y', 'z' ]

有什么建议吗?如果有更简单的方法,请打开辅助库。

2 个答案:

答案 0 :(得分:1)

您应该可以使用json-pointer来做您想做的事。

该模块包含一个dict函数,该函数返回一个包含JSON指针键及其相关值的对象。例如:

var jsonPointer = require("json-pointer");

var data = {
    users: {
        abc: {
            age: 23
        },
        xyz: {
            hair: "blue"
        }
    }
};
var dict = jsonPointer.dict(data);

结果字典将是:

{
    "/users/abc/age": 23,
    "/users/xyz/hair": "blue"
}

键/路径将以斜杠开头,但Firebase仍将它们视为相对于调用update的参考号。

答案 1 :(得分:0)

[我有一种工作方法,但我不会accept这个答案,希望有更多专业知识的人可以在我这里发表意见/纠正我错了]

更改我将patharray存储到string的变量,以及对OP中算法的其他一些更改,此解决方案似乎有效远:

export const flattenForUpdate = (obj, path = '') => {
  var newData = {}

  const _flatten = (_obj, _path) => {
    try {
        for (var propName in _obj) {
            if (_obj.hasOwnProperty(propName)) {
                if (typeof _obj[propName] === "object" 
                  && typeof _obj[propName] !== null
                  && Array.isArray(_obj[propName]) === false) {
                    _flatten(_obj[propName], _path + "/" + String(propName))
                } else if (_obj[propName] == null) {
                    throw new Error("Null/Undefined in Firebase data update payload!")
                } else {
                    let thisPath = _path + "/" + String(propName)
                    newData[thisPath] = _obj[propName]
                }
            }
        }
    } catch (err) {
        console.error("ERROR IN FLATTENING", err)
    }
  }
  _flatten(obj, path)
  return newData

 }

它有点乱,但关键要点是:

  • 它会返回Arrays就像primitive,b / c arrays在firebase中很奇怪,并且不能真正支持原子粒度更新,所以最简单的情况是覆盖firebase中的当前数组
  • 它将会null以及undefinedundefined有效负载中的update()会导致错误阻止整个写入,null删除无论有什么价值,这可能都是不可取的
  • 这是一个闭包(我认为)。我从未在afaik之前使用closures,但据我所知,因为它是递归的,所以任何内部状态都必须保持在与执行实际递归的函数不同的context中 - 显然是为什么上帝/ ECMA /道格拉斯克罗克福德或任何制造JS的人都包括JS。