通过深度嵌套的 object/array 递归搜索并在不知道键的情况下收集包含指定字符串的值
Search recursively through a deeply nested object/array and collect values that include a specified string without knowing the key(s)
假设我有一个深度嵌套的对象或数组,从 API 返回,它可能如下所示:
const array = [
{
key1: 'dog',
key2: 'cat'
},
{
key1: 'dog',
key2: 'cat'
key3: [
{
keyA: 'mouse',
keyB: 'https://myURL.com/path/to/file/1'
keyC: [
{
keyA: 'tiger',
keyB: 'https://myURL.com/path/to/file/2'
}
]
}
]
},
{
key4: 'dog',
key5: 'https://myURL.com/path/to/file/3'
}
]
我想递归遍历 object/array 并构建一个包含所有字符串值的数组并包含子字符串 https://myURL.com
,并构建一个包含匹配项的数组,如下所示:
let matches = [
'https://myURL.com/path/to/file/1',
'https://myURL.com/path/to/file/2',
'https://myURL.com/path/to/file/3'
]
重要的一点是,我事先并不知道相关的密钥是什么。 URL 的值可能位于任何键上。因此,在此示例中,我不能简单地查找 keyB
或 key5
- 我必须测试每个 key:value
对以查找包含 URL 字符串的值。
问题
如何创建递归函数来搜索对象中包含 .includes() 字符串 'https://myURL.com' 的值?很高兴使用 lodash 方法,所以请随时提交依赖于 lodash 的解决方案。
上下文
数据来自 CMS API,我将使用 URLS 的数组(在本例中为上面的 matches
)到 add assets to cache with Cache API,异步在后台,用于 PWA 的离线功能。
非常感谢。
此递归函数查找与谓词匹配的叶节点:
const getMatches = (pred) => (obj) =>
obj == null ?
[]
: Array .isArray (obj)
? obj .flatMap (getMatches (pred))
: typeof obj == 'object'
? Object .values (obj) .flatMap (getMatches (pred))
: pred (obj)
? obj
: []
const array = [{key1: 'dog', key2: 'cat'},{key1: 'dog', key2: 'cat', key3: [{keyA: 'mouse', keyB: 'https://myURL.com/path/to/file/1', keyC: [{keyA: 'tiger', keyB: 'https://myURL.com/path/to/file/2'}]}]}, {key4: 'dog',key5: 'https://myURL.com/path/to/file/3'}]
const check = val => typeof val == 'string' && val .includes ('https://myURL.com')
console .log (
getMatches (check) (array)
)
我们可以修改它以也包括非叶节点。但这似乎是您在这种情况下所需要的。
我们只需将谓词传递给它来测试我们的节点,然后将要测试的对象或数组传递给结果函数。它将 return 匹配的节点。
如果愿意,您可以通过不直接传递对象来保存中间函数:
const getMyUrls = getMatches (check)
// ... later
const urls = getMyUrls (someObject);
更新
谢谢你的回答让我意识到我的功能可以进一步简化。这是它的更好版本:
const getMatches = (pred) => (obj) =>
Object (obj) === obj
? Object .values (obj) .flatMap (getMatches (pred))
: pred (obj)
? obj
: []
这是使用 values
和 filter
生成器的通用方法 -
const values = function* (t)
{ if (Object(t) === t)
for (const v of Object.values(t))
yield* values(v)
else
yield t
}
const filter = function* (test, t)
{ for (const v of values(t))
if (test(v))
yield v
}
我们现在可以使用 filter
-
编写一个简单的 query
const myArray =
[ ... ]
const query =
filter
( v =>
String(v) === v
&& v.startsWith("https://myURL.com")
, myArray
)
const result =
Array.from(query)
console.log(result)
// [ 'https://myURL.com/path/to/file/1'
// , 'https://myURL.com/path/to/file/2'
// , 'https://myURL.com/path/to/file/3'
// ]
使用像 filter
这样的泛型,我们可以编写像 searchByString
-
这样的特殊变体
const searchByString = (test, t) =>
filter // <-- specialisation
( v => String(v) === v && test(v) // <-- auto reject non-strings
, t
)
现在调用站点的查询更清晰了 -
const myArray =
[ ... ]
const query =
searchByString // <-- specialised
( v => v.startsWith("https://myURL.com") // <-- simplified filter
, myArray
)
const result =
Array.from(query)
console.log(result)
// [ 'https://myURL.com/path/to/file/1'
// , 'https://myURL.com/path/to/file/2'
// , 'https://myURL.com/path/to/file/3'
// ]
展开下面的代码片段以在浏览器中验证结果 -
const myArray =
[{key1:'dog',key2:'cat'},{key1:'dog',key2:'cat',key3:[{keyA:'mouse',keyB:'https://myURL.com/path/to/file/1',keyC:[{keyA:'tiger',keyB:'https://myURL.com/path/to/file/2'}]}]},{key4:'dog',key5:'https://myURL.com/path/to/file/3'}]
const values = function* (t)
{ if (Object(t) === t)
for (const v of Object.values(t))
yield* values(v)
else
yield t
}
const filter = function* (test, t)
{ for (const v of values(t))
if (test(v))
yield v
}
const searchByString = (test, t) =>
filter
( v =>
String(v) === v && test(v)
, t
)
const query =
searchByString
( v => v.startsWith("https://myURL.com")
, myArray
)
const result =
Array.from(query)
console.log(result)
// [ 'https://myURL.com/path/to/file/1'
// , 'https://myURL.com/path/to/file/2'
// , 'https://myURL.com/path/to/file/3'
// ]
假设我有一个深度嵌套的对象或数组,从 API 返回,它可能如下所示:
const array = [
{
key1: 'dog',
key2: 'cat'
},
{
key1: 'dog',
key2: 'cat'
key3: [
{
keyA: 'mouse',
keyB: 'https://myURL.com/path/to/file/1'
keyC: [
{
keyA: 'tiger',
keyB: 'https://myURL.com/path/to/file/2'
}
]
}
]
},
{
key4: 'dog',
key5: 'https://myURL.com/path/to/file/3'
}
]
我想递归遍历 object/array 并构建一个包含所有字符串值的数组并包含子字符串 https://myURL.com
,并构建一个包含匹配项的数组,如下所示:
let matches = [
'https://myURL.com/path/to/file/1',
'https://myURL.com/path/to/file/2',
'https://myURL.com/path/to/file/3'
]
重要的一点是,我事先并不知道相关的密钥是什么。 URL 的值可能位于任何键上。因此,在此示例中,我不能简单地查找 keyB
或 key5
- 我必须测试每个 key:value
对以查找包含 URL 字符串的值。
问题
如何创建递归函数来搜索对象中包含 .includes() 字符串 'https://myURL.com' 的值?很高兴使用 lodash 方法,所以请随时提交依赖于 lodash 的解决方案。
上下文
数据来自 CMS API,我将使用 URLS 的数组(在本例中为上面的 matches
)到 add assets to cache with Cache API,异步在后台,用于 PWA 的离线功能。
非常感谢。
此递归函数查找与谓词匹配的叶节点:
const getMatches = (pred) => (obj) =>
obj == null ?
[]
: Array .isArray (obj)
? obj .flatMap (getMatches (pred))
: typeof obj == 'object'
? Object .values (obj) .flatMap (getMatches (pred))
: pred (obj)
? obj
: []
const array = [{key1: 'dog', key2: 'cat'},{key1: 'dog', key2: 'cat', key3: [{keyA: 'mouse', keyB: 'https://myURL.com/path/to/file/1', keyC: [{keyA: 'tiger', keyB: 'https://myURL.com/path/to/file/2'}]}]}, {key4: 'dog',key5: 'https://myURL.com/path/to/file/3'}]
const check = val => typeof val == 'string' && val .includes ('https://myURL.com')
console .log (
getMatches (check) (array)
)
我们可以修改它以也包括非叶节点。但这似乎是您在这种情况下所需要的。
我们只需将谓词传递给它来测试我们的节点,然后将要测试的对象或数组传递给结果函数。它将 return 匹配的节点。
如果愿意,您可以通过不直接传递对象来保存中间函数:
const getMyUrls = getMatches (check)
// ... later
const urls = getMyUrls (someObject);
更新
谢谢你的回答让我意识到我的功能可以进一步简化。这是它的更好版本:
const getMatches = (pred) => (obj) =>
Object (obj) === obj
? Object .values (obj) .flatMap (getMatches (pred))
: pred (obj)
? obj
: []
这是使用 values
和 filter
生成器的通用方法 -
const values = function* (t)
{ if (Object(t) === t)
for (const v of Object.values(t))
yield* values(v)
else
yield t
}
const filter = function* (test, t)
{ for (const v of values(t))
if (test(v))
yield v
}
我们现在可以使用 filter
-
query
const myArray =
[ ... ]
const query =
filter
( v =>
String(v) === v
&& v.startsWith("https://myURL.com")
, myArray
)
const result =
Array.from(query)
console.log(result)
// [ 'https://myURL.com/path/to/file/1'
// , 'https://myURL.com/path/to/file/2'
// , 'https://myURL.com/path/to/file/3'
// ]
使用像 filter
这样的泛型,我们可以编写像 searchByString
-
const searchByString = (test, t) =>
filter // <-- specialisation
( v => String(v) === v && test(v) // <-- auto reject non-strings
, t
)
现在调用站点的查询更清晰了 -
const myArray =
[ ... ]
const query =
searchByString // <-- specialised
( v => v.startsWith("https://myURL.com") // <-- simplified filter
, myArray
)
const result =
Array.from(query)
console.log(result)
// [ 'https://myURL.com/path/to/file/1'
// , 'https://myURL.com/path/to/file/2'
// , 'https://myURL.com/path/to/file/3'
// ]
展开下面的代码片段以在浏览器中验证结果 -
const myArray =
[{key1:'dog',key2:'cat'},{key1:'dog',key2:'cat',key3:[{keyA:'mouse',keyB:'https://myURL.com/path/to/file/1',keyC:[{keyA:'tiger',keyB:'https://myURL.com/path/to/file/2'}]}]},{key4:'dog',key5:'https://myURL.com/path/to/file/3'}]
const values = function* (t)
{ if (Object(t) === t)
for (const v of Object.values(t))
yield* values(v)
else
yield t
}
const filter = function* (test, t)
{ for (const v of values(t))
if (test(v))
yield v
}
const searchByString = (test, t) =>
filter
( v =>
String(v) === v && test(v)
, t
)
const query =
searchByString
( v => v.startsWith("https://myURL.com")
, myArray
)
const result =
Array.from(query)
console.log(result)
// [ 'https://myURL.com/path/to/file/1'
// , 'https://myURL.com/path/to/file/2'
// , 'https://myURL.com/path/to/file/3'
// ]