在map中查找可能的路径:JavaScript中的递归函数



doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
}
}
}
}
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
// console.log("Obj check" + JSON.stringify(nObj) + keystr)
arr.push(nObj);
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = (get(temp, concat.substr(0, concat.length - 1)));
if (obj != "") {
// console.log("existing arr "+JSON.stringify(arr))
obj[m] = (obj[0])[concat.substr(0, concat.length - 1)]
//  console.log("hello "+JSON.stringify(obj) + " end hello")
arr.push(obj);
}
} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
return arr;
}
var result = (get(doc, 'a.*.e'))
console.log(result)

对于'a.*.e'的输入,输出应{'a.d.e': {'f': 'blah blah blah'}}}。但是我也在数组中得到了通配符的所有替换。我确定有问题,但无法检测到它。帮助将不胜感激。

您可以使用递归方法稍微更改操作的结构,并且通常使用退出选项检查单个部分的范例,例如

  • 长度,找到零件结果,
  • 虚假或非对象类型,
  • 索引处的部分是一个星号,然后迭代对象中的所有键,或者
  • 索引处的部分是一个键,然后再次调用该函数。

最后,使用找到的路径,连接路径并生成具有对象实际值的新属性。

function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

*作为任何级别的通配符的版本。

function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i);
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
console.log(get(doc, 'a.*.f'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

首先,由于所需的输出{'a.d.e': {'f': 'blah blah blah'}}}不包含任何数组,而只包含纯对象,因此代码中不需要变量arr

相反,返回nObj作为函数结果,并在开始时声明它,从不清除它。

其次,当您从递归调用返回时,需要复制结果,同时在路径前面加上您已有的内容。请注意,检查空数组不应该使用!= ""来完成,但无论如何,您不再需要它了。

你可以用不同的方式从头开始编写它(请参阅答案末尾的解决方案),但我首先调整了您的代码以仅更改最低限度,并在我进行更改以使其工作的地方添加了注释:

function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
// *** Define here the object to return
var nObj = {};

for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
// *** Move this to start of the function
//var nObj = {}; 
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = get(temp, concat.substr(0, concat.length - 1));
// *** Return value is object with path(s) as keys
// *** Don't compare array with string
//if (arr != "") { 
// *** Iterate over the returned object properties, and prefix them 
for (var deepKey in obj) {
nObj[keystr + deepKey] = obj[deepKey];
}
//*** No need for array; we already have the object properties
//arr.push(obj);
//}
// *** No need for array
//} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
// *** Return object 
return nObj;
}
var doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
}
var result = (get(doc, 'a.*.e'));
console.log(result);

还请考虑不要命名对象json当它们不是:JSON 是一种文本格式,JavaScript 对象变量与 JSON 不是一回事。

紧凑型 ES6 解决方案

当您习惯于数组函数(如reduce函数式编程风格)时,以下紧凑的 ES6 解决方案可能会吸引您:

function get(obj, path) {
if (typeof path === 'string') path = path.split('.');
return !path.length ? { '': obj } // Match
: obj !== Object(obj) ? {} // No match
: (path[0] === '*' ? Object.keys(obj) : [path[0]]) // Candidates
.reduce( (acc, key) => {
const match = get(obj[key], path.slice(1)); // Recurse
return Object.assign(acc, ...Object.keys(match).map( dotKey => 
({ [key + (dotKey ? '.'  + dotKey : '')]: match[dotKey] })
));
}, {});
}
const doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
};
const result = get(doc, 'a.*.e');
console.log(result);

list monad

这是一个解决方案,它借用了列表 monad 的想法来表示可能具有 0、1 或更多结果的计算。我不打算详细介绍它,我只包含了足够的List类型来获得一个可行的解决方案。如果你对这种方法感兴趣,你可以对这个主题做更多的研究,或者问我一个后续问题。

我还使用了一个辅助find函数,它是get的递归助手,它操作get准备的键数组

如果你喜欢这个解决方案,我已经在其他一些答案中写过关于列表monad的文章;你可能会发现它们很有帮助^_^。

const List = xs =>
({
value:
xs,
bind: f =>
List (xs.reduce ((acc, x) =>
acc.concat (f (x) .value), []))
})
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return List([{ [path.join('.')]: data }])
else if (key === '*')
return List (Object.keys (data)) .bind (k =>
find ([...path, k], keys, data[k]))
else if (data[key] === undefined)
return List ([])
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find ([], path.split ('.'), doc) .value
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}

console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]

console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
//   { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]


仅限本机数组

我们不必为了获得相同的结果而做花哨List抽象。在此版本的代码中,我将向您展示如何仅使用本机数组来执行此操作。此代码的唯一缺点是'*'-key 分支通过嵌入与我们的函数内联的平面映射代码而变得有点复杂

const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return [{ [path.join ('.')]: data }]
else if (key === '*')
return Object.keys (data) .reduce ((acc, k) =>
acc.concat (find ([...path, k], keys, data[k])), [])
else if (data[key] === undefined)
return []
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find([], path.split('.'), doc)
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
//   { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]


为什么我推荐列表单子

我个人确实推荐列表monad方法,因为它可以使find函数的主体保持最干净。它还包含模棱两可计算的概念,并允许您在可能需要此类行为的任何地方重用它。如果不使用 List monad,您每次都会重写必要的代码,这会给理解代码增加很多认知负担。


调整结果的形状

函数的返回类型非常奇怪。我们返回一个只有一个键/值对的对象数组。键是我们找到数据的路径,值是匹配的数据。

通常,我们不应该以这种方式使用 Object 键。我们将如何显示比赛结果?

// get ('a.*', doc) returns
let result =
[ { 'a.b': { c: 'hello' } },
{ 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
result.forEach (match =>
Object.keys (match) .forEach (path =>
console.log ('path:', path, 'value:', match[path])))

// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }

如果我们返回[<key>, <value>]而不是{<key>: <value>}怎么办?使用这种形状的结果要舒服得多。支持此功能的其他原因是数据形状更好,例如Array#entriesMap#entries()

// get ('a.*', doc) returns proposed
let result =
[ [ 'a.b', { c: 'hello' } ],
[ 'a.d', { c: 'sup', e: { f: 'blah blah blah' } } ] ]
for (let [path, value] of result)
console.log ('path:', path, 'value:', value)
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }

如果您同意这是一个更好的形状,更新代码很简单(以粗体更改)

// List monad version
const find = (path, [key, ...keys], data) =>{
if (key === undefined)
return List ([[path.join ('.'), data]])
...
}
// native arrays version
const find = (path, [key, ...keys], data) =>{
if (key === undefined)
return [[path.join ('.'), data]]
...
}

最新更新