如何从JavaScript对象中动态检索任意指定的深度嵌套值



作为此问题的更正版本重新发布。我已经复习过了,但它并没有帮助我处理未知数组嵌套。

编辑:添加了Lodash_.get无法工作的原因信息。这是斯塔克布利茨。有一个npm库,对象扫描可以工作——但是,我不能让Angular很好地使用这个包。

这是针对Angular应用程序中的列渲染器组件。用户选择要显示的列,并且该列被映射到它是什么;类别";,并且它被映射到"0";兴趣[类别]";,然后,函数需要在数据中查找与该映射相对应的值。

样本数据:

const response = {
id: "1234",
version: "0.1",
drinks: ["Scotch"],
interests: [
{
categories: ["baseball", "football"],
refreshments: {
drinks: ["beer", "soft drink"]
}
},
{
categories: ["movies", "books"],
refreshments: {
drinks: ["coffee", "tea", "soft drink"]
}
}
],
goals: [
{
maxCalories: {
drinks: "350",
pizza: "700"
}
}
]
};

对于这些数据,可能的映射为:CCD_ 2,CCD_ 3,CCD_ 4,CCD_ 5,CCD_ 6,CCD_ 7,goals[maxCalories][pizza]

要求:我需要一个在Angular中工作的递归函数,并接受两个参数:一个用于数据对象response,另一个用于选择器映射的字符串,它将返回适用的、可能嵌套的值,在遇到的任何对象和/或数组中迭代。映射字符串可以具有点符号或方括号符号。可读的、自文档化的代码是非常需要的。

例如,基于上述数据对象:

  • getValues("id", response);应返回["1234"]
  • getValues("version", response);应返回["0.1"]
  • getValues("drinks", response);应返回["Scotch"]
  • getValues("interests.categories", response)应返回["baseball", "football", "movies", "books"]
  • getValues("interests[refreshments][drinks]", response);应返回["beer", "soft drink", "coffee", "tea", "soft drink" ],说明id0数组中可能有多少项
  • getValues("goals.maxCalories.drinks", response);应返回["350"]

返回的值最终将进行重复数据消除和排序。

原始功能:

function getValues(mapping, row) {
let rtnValue = mapping
.replace(/]/g, "")
.split("[")
.map(item => item.split("."))  
.reduce((arr, next) => [...arr, ...next], [])
.reduce((obj, key) => obj && obj[key], row)
.sort();
rtnValue = [...new Set(rtnValue)]; //dedupe the return values
return rtnValue.join(", ");
}

上面的操作很好,就像Lodash的get一样:如果你通过,例如:"interests[0][refreshments][drinks],它就可以完美地工作。但是,映射不知道嵌套的数组,只传递"interests[refreshments][drinks]",这导致了undefined。任何后续的数组项都必须考虑在内。

更新

在我给出下面的答案后,我吃了一些晚饭。食物似乎让我意识到自己有多么可笑。我对前面问题的回答清楚地表明了我对这个问题的看法。但是有一种非常简单的方法,类似于我编写name2path函数的方法,只需要少量的额外复杂性来处理数组的映射。这里有一个更干净的方法:

.as-console-wrapper {max-height: 100% !important; top: 0}
// utility functions
const isNumber = (n) =>
Number(n) === n
const path = (ps) => (obj) =>
ps .reduce ((o, p) => (o || {}) [p], obj)
const last = (xs) =>
xs [xs .length - 1]

const getPaths = (obj, arr = Array .isArray (obj)) =>
typeof obj == 'object' 
? Object .entries (obj) 
.flatMap (([k, v]) => [
[arr ? Number (k): k], 
...getPaths (v) .map (p => [arr ? Number (k): k, ...p])
])
: []

// helper function
const name2path = (name) => // probably not a full solutions, but ok for now
name .split (/[[].]+/g) .filter (Boolean)

// main function
const getValues = (query, obj, key = name2path (query) .join ('~')) => 
getPaths (obj)
.filter (
p => p.filter(n => ! isNumber (n)) .join ('~') == key && ! isNumber (last (p))
)
.flatMap (p => path (p) (obj))

// sample data
const response = {id: "1234", version: "0.1", drinks: ["Scotch"], interests: [{categories: ["baseball", "football"], refreshments: {drinks: ["beer", "soft drink"]}}, {categories: ["movies", "books"], refreshments: {drinks: ["coffee", "tea", "soft drink"]}}], goals: [{maxCalories: {drinks: "350", pizza: "700"}}]};

// demo
[
'id',
'version',
'drinks',
'interests[categories]',
'interests[refreshments][drinks]',
'goals.maxCalories.drinks',
] .forEach (
name => console.log(`"${name}" --> ${JSON.stringify(getValues(name, response))}`)
)

关于它没什么好说的。它是一个足够简单的递归,仍然有一个单独的包装器函数,可以将字符串输入格式转换为数组,我觉得这更自然地用于这些事情。(因为这个函数非常简单,所以我内联了以前版本中使用的getPaths函数

道德

下面的错误版本有一个强烈的寓意:将需求的更改视为更改代码的指令太诱人了。我们总是应该尝试将新的需求视为重新思考我们的方法的时候,并可能重写我们的代码。当它导致一个更简单的系统时,这样做是值得的。在这里,它显然做到了。

初步答复

(这应该被认为完全被上面的更新所取代。我把它放在这里是为了表明当你有新的需求时,忘记从一开始就考虑问题是愚蠢的。上面的方法非常干净,所以这个版本现在看起来很愚蠢。)

我现在没有太多时间来描述这种技术。我在回答前面的问题时所说的大部分内容仍然适用于此代码。CCD_ 29被增强了一位以返回用于数组索引的数字节点。

主函数version0首先获取当前对象中的所有路径(值为'~')。它还通过将所有值与flatMap连接,将查询转换为键。然后,当我们删除数值时,我们过滤路径以找到与键匹配的路径。同时,我们删除了那些以数字结尾的人,因为他们的父母会给我们所需要的所有价值观。然后,我们path调用get来检索原始对象中这些路径中的每个路径的值。

前面的答案相当严重地误解了您的需求,但无论如何都是一个有趣的函数。我认为这更接近你真正需要的。

const {tokenizePath, resolveValue} = require('path-value');
function getValues(path, obj) {
const tokens = tokenizePath(path); // to support full ES5 syntax
return resolveValue(obj, tokens);
}
getValues("id", response); //=> 1234

此方法的一个限制是查询参数不能包含数字索引。这意味着您不能只搜索某个数组的第一个实例。虽然我确信我们可以更改这一点以允许这样做,但我猜代码会比我们这里的代码复杂得多。

为什么要重新发明轮子?您所描述的正是Lodash CCD_35函数的功能。看见https://lodash.com/docs/4.17.15#get

如果你真的想拥有一个";香草";解决方案,查看他们是如何做到的。请参阅https://github.com/lodash/lodash/blob/master/get.js

使用路径值语法:

PD_7

最新更新