在深层嵌套对象中按特定键查找所有值

Posted

技术标签:

【中文标题】在深层嵌套对象中按特定键查找所有值【英文标题】:Find all values by specific key in a deep nested object 【发布时间】:2019-07-18 07:50:40 【问题描述】:

如何?

例如,如果我有这样的对象:

const myObj = 
  id: 1,
  children: [
    
      id: 2,
      children: [
        
          id: 3
        
      ]
    ,
    
      id: 4,
      children: [
        
          id: 5,
          children: [
            
              id: 6,
              children: [
                
                  id: 7,
                
              ]
            
          ]
        
      ]
    ,
  ]

如何通过id 的键在这个 obj 的所有嵌套中获取所有值的数组。

注意:children 是一个一致的名称,id 不会存在于 children 对象之外。

所以从 obj 中,我想生成一个这样的数组:

const idArray = [1, 2, 3, 4, 5, 6, 7]

【问题讨论】:

【参考方案1】:

这有点晚了,但对于其他发现它的人来说,这是一个干净的通用递归函数:

function findAllByKey(obj, keyToFind) 
  return Object.entries(obj)
    .reduce((acc, [key, value]) => (key === keyToFind)
      ? acc.concat(value)
      : (typeof value === 'object')
      ? acc.concat(findAllByKey(value, keyToFind))
      : acc
    , [])


// USAGE
findAllByKey(myObj, 'id')

【讨论】:

超级干净,效果很好,易于剪切和粘贴。最佳答案。 这为我节省了一些时间,而且考虑启动也很有趣。谢谢! 将第 5 行更改为 : (typeof value === 'object' && value),因为出于某种原因,typeof null === "object"【参考方案2】:

你可以像这样创建一个递归函数:

idArray = []

function func(obj) 
  idArray.push(obj.id)
  if (!obj.children) 
    return
  

  obj.children.forEach(child => func(child))

您的示例代码段:

const myObj = 
  id: 1,
  children: [
      id: 2,
      children: [
        id: 3
      ]
    ,
    
      id: 4,
      children: [
        id: 5,
        children: [
          id: 6,
          children: [
            id: 7,
          ]
        ]
      ]
    ,
  ]


idArray = []

function func(obj) 
  idArray.push(obj.id)
  if (!obj.children) 
    return
  

  obj.children.forEach(child => func(child))


func(myObj)
console.log(idArray)

【讨论】:

【参考方案3】:

您可以创建一个适用于任何属性和任何对象的通用递归函数。

这使用Object.entries()Object.keys()Array.reduce()Array.isArray()Array.map()Array.flat()

停止条件是传入的对象为空时:

const myObj = 
  id: 1,
  anyProp: [
    id: 2,
    thing:  a: 1, id: 10 ,
    children: [ id: 3 ]
  , 
    id: 4,
    children: [
      id: 5,
      children: [
        id: 6,
        children: [ id: 7 ]
      ]
    ]
  ]
;

const getValues = prop => obj => 
  if (!Object.keys(obj).length)  return []; 

  return Object.entries(obj).reduce((acc, [key, val]) => 
    if (key === prop) 
      acc.push(val);
     else 
      acc.push(Array.isArray(val) ? val.map(getIds).flat() : getIds(val));
    
    return acc.flat();
  , []);


const getIds = getValues('id');

console.log(getIds(myObj));

【讨论】:

【参考方案4】:

注意:children 是一致的名称,id 不会存在于外部 children 对象。

所以从 obj 中,我想生成一个这样的数组:

const idArray = [1, 2, 3, 4, 5, 6, 7]

鉴于该问题不包含对如何从输入导出输出的任何限制,并且输入是一致的,其中属性"id" 的值是一个数字,而id 属性仅在@987654327 内定义@ 属性,除了对象中第一个 "id" 的情况外,输入的 javascript 普通对象可以使用 JSON.stringify() 转换为 JSON 字符串,RegExp /"id":\d+/g 匹配 "id" 属性和一个或属性名称后面还有更多数字字符,然后使用 Regexp \d+ 将其映射到上一个匹配的数字部分 .match() 并使用加法运算符 + 将数组值转换为 JavaScript 数字

const myObject = "id":1,"children":["id":2,"children":["id":3],"id":4,"children":["id":5,"children":["id":6,"children":["id":7]]]];

let res = JSON.stringify(myObject).match(/"id":\d+/g).map(m => +m.match(/\d+/));

console.log(res);

JSON.stringify() replacer 函数也可以用于.push() 对象内每个"id" 属性名称的值到一个数组中

const myObject = "id":1,"children":["id":2,"children":["id":3],"id":4,"children":["id":5,"children":["id":6,"children":["id":7]]]];

const getPropValues = (o, prop) => 
  (res => (JSON.stringify(o, (key, value) => 
    (key === prop && res.push(value), value)), res))([]);

let res = getPropValues(myObject, "id");

console.log(res);

由于要匹配的输入的属性值是数字,所有的JavaScript对象都可以转换成字符串,RegExp\D可以用来替换所有不是数字的字符,将得到的字符串展开到数组中, 和 .map() 数字转换为 JavaScript 数字

let res = [...JSON.stringify(myObj).replace(/\D/g,"")].map(Number)

【讨论】:

【参考方案5】:

使用递归。

const myObj =   id: 1,  children: [          id: 2,      children: [                  id: 3              ]    ,          id: 4,      children: [                  id: 5,          children: [                          id: 6,              children: [                                  id: 7,                              ]                      ]              ]    ,  ],
    loop = (array, key, obj) => 
      if (!obj.children) return;
      obj.children.forEach(c => 
        if (c[key]) array.push(c[key]); // is not present, skip!
        loop(array, key, c);
      );
    ,
    arr = myObj["id"] ? [myObj["id"]] : [];
    
loop(arr, "id", myObj);
console.log(arr);
.as-console-wrapper  max-height: 100% !important; top: 0; 

【讨论】:

【参考方案6】:

我发现 steve 的答案最适合我在推断这一点并创建通用递归函数时的需要。也就是说,我在处理空值和未定义值时遇到了问题,因此我扩展了条件以适应这种情况。这种方法使用:

Array.reduce() - 它使用累加器函数将值附加到结果数组中。它还将每个对象拆分为它的键:值对,这允许您采取以下步骤:

    你找到钥匙了吗?如果是,则将其添加到数组中; 如果没有,我是否找到了具有值的对象?如果是这样,钥匙可能就在里面。通过调用此对象上的函数并将结果附加到结果数组中继续挖掘;和 最后,如果这不是一个对象,则返回结果数组不变。

希望对你有帮助!

const myObj = 
  id: 1,
  children: [
      id: 2,
      children: [
        id: 3
      ]
    ,
    
      id: 4,
      children: [
        id: 5,
        children: [
          id: 6,
          children: [
            id: 7,
          ]
        ]
      ]
    ,
  ]


function findAllByKey(obj, keyToFind) 
  return Object.entries(obj)
    .reduce((acc, [key, value]) => (key === keyToFind)
      ? acc.concat(value)
      : (typeof value === 'object' && value)
      ? acc.concat(findAllByKey(value, keyToFind))
      : acc
    , []) || [];


const ids = findAllByKey(myObj, 'id');

console.log(ids)

【讨论】:

内部错误:递归过多 你有示例对象吗? 太棒了。正是我需要的!效果很好。【参考方案7】:

您可以像这样使用Object.entries 创建一个递归函数:

const myObj = 
  id: 1,
  children: [
      id: 2,
      children: [
        id: 3
      ]
    ,
    
      id: 4,
      children: [
        id: 5,
        children: [
          id: 6,
          children: [
            id: 7,
          ]
        ]
      ]
    ,
  ]
;

function findIds(obj) 
  const entries = Object.entries(obj);
  let result = entries.map(e => 
    if (e[0] == "children") 
      return e[1].map(child => findIds(child));
     else 
      return e[1];
    
  );
  function flatten(arr, flat = []) 
    for (let i = 0, length = arr.length; i < length; i++) 
      const value = arr[i];
      if (Array.isArray(value)) 
        flatten(value, flat);
       else 
        flat.push(value);
      
    
    return flat;
  
  return flatten(result);


var ids = findIds(myObj);
console.log(ids);

来自this answer的扁平化函数

ES5 语法:

var myObj = 
  id: 1,
  children: [
      id: 2,
      children: [
        id: 3
      ]
    ,
    
      id: 4,
      children: [
        id: 5,
        children: [
          id: 6,
          children: [
            id: 7,
          ]
        ]
      ]
    ,
  ]
;

function findIds(obj) 
  const entries = Object.entries(obj);
  let result = entries.map(function(e) 
    if (e[0] == "children") 
      return e[1].map(function(child) 
        return findIds(child)
      );
     else 
      return e[1];
    
  );
  function flatten(arr, flat = []) 
    for (let i = 0, length = arr.length; i < length; i++) 
      const value = arr[i];
      if (Array.isArray(value)) 
        flatten(value, flat);
       else 
        flat.push(value);
      
    
    return flat;
  
  return flatten(result);


var ids = findIds(myObj);
console.log(ids);

【讨论】:

没问题@cup_of,我很乐意提供帮助【参考方案8】:
let str = JSON.stringify(myObj);
let array = str.match(/\d+/g).map(v => v * 1);
console.log(array); // [1, 2, 3, 4, 5, 6, 7]

【讨论】:

啊,非常聪明的方法。 +1【参考方案9】:

我们现在使用object-scan 来满足我们的很多数据处理需求。它使代码更易于维护,但确实需要一点时间来理解。以下是您如何使用它来回答您的问题

// const objectScan = require('object-scan');

const find = (data, needle) => objectScan([needle],  rtn: 'value' )(data);

const myObj =  id: 1, children: [ id: 2, children: [  id: 3  ] ,  id: 4, children: [  id: 5, children: [  id: 6, children: [  id: 7  ]  ]  ] ] ;

console.log(find(myObj, '**.id'));
// => [ 7, 6, 5, 4, 3, 2, 1 ]
.as-console-wrapper max-height: 100% !important; top: 0
&lt;script src="https://bundle.run/object-scan@13.7.1"&gt;&lt;/script&gt;

免责声明:我是object-scan的作者

【讨论】:

【参考方案10】:
import flattenDeep from 'lodash';

/**
 * Extracts all values from an object (also nested objects)
 * into a single array
 *
 * @param obj
 * @returns
 *
 * @example
 * const test = 
 *  alpha: 'foo',
 *  beta: 
 *    gamma: 'bar',
 *    lambda: 'baz'
 *  
 * 
 *
 * objectFlatten(test) // ['foo', 'bar', 'baz']
 */
export function objectFlatten(obj: ) 
  const result = [];
  for (const prop in obj) 
    const value = obj[prop];
    if (typeof value === 'object') 
      result.push(objectFlatten(value));
     else 
      result.push(value);
    
  
  return flattenDeep(result);

【讨论】:

如果你写一个关于你的示例代码的解释会很有帮助。 @chrwahl 请查看上面代码的 jsdocs 注释,甚至附带示例。【参考方案11】:

下面的解决方案是通用的,它将通过匹配嵌套键返回所有值,例如下面的 json 对象


   "a":1,
   "b":
      "a":
         "a":"red"
      
   ,
   "c":
      "d":2
   

要查找与键“a”匹配的所有值,输出应返回

[1,a:"red","red"]
const findkey = (obj, key) => 
  let arr = [];
  if (isPrimitive(obj)) return obj;

  for (let [k, val] of Object.entries(obj)) 
    if (k === key) arr.push(val);
    if (!isPrimitive(val)) arr = [...arr, ...findkey(val, key)];
  
  return arr;
;

const isPrimitive = (val) => 
  return val !== Object(val);
;

【讨论】:

以上是关于在深层嵌套对象中按特定键查找所有值的主要内容,如果未能解决你的问题,请参考以下文章

javascript在嵌套对象/数组中按值查找

Json Schema:仅当深层嵌套对象中存在特定属性时才需要属性

在嵌套字典中查找特定键的值

如何在嵌套的 JS 对象中查找特定属性的总和

从嵌套数组对象中查找对象

如何找到所有可见的深层嵌套 ListView 项目?