Search key and return value in nested object

2020-05-03 11:41发布

问题:

Lets say my object looks like the below. Its a mix of different arrays and parents, no hierarchical order. e.g.

"person": {
"id": 12345,
"name": "John Doe",
"emergencyContacts": [
  {
    "name": "Jane Doe",
    "phone": "888-555-1212",
    "relationship": "spouse",
    "moreDetails": {
      "id": 12345,
      "phones": {},
      "home": "800-123-4567",
      "mobile": "877-123-1234"
    }
  },
  {
    "name": "Justin Doe",
    "phone": "877-123-1212",
    "relationship": "parent",
    "mobile": "877-123-1234"
  }
],
"workContacts": [
  {
    "name": "Jane Doe",
    "phone": "888-555-1212",
    "relationship": "spouse",
    "moreworkDetails": {
      "id": 12345,
      "phones": {},
      "home": "800-123-4567",
      "mobile": "877-123-1234"
    }
  },
  {
    "name": "Justin Doe",
    "phone": "877-123-1212",
    "relationship": "parent",
    "mobile": "877-123-1234"
  }
]
}

I want to be able to search the entire object of person and bring back the value of the key mobile. I am guessing that the object needs to be flattened and a new array created with the list of "unique" mobile numbers found e.g.

var mobile = { 
0: 888-555-1212,
1: 800-123-4567,
2: 877-123-1234,
3: 083-111-3346
}

I have searched for solutions in pure js and lodash, however each one found is knowing what the parent is called and depth of the array - lets assume the parent and depth could be limitless(this sets it aside from question Find by key deep in a nested object). Any help would be muchly appreciated. Thanks

回答1:

You will need a recursive function. For explanation please check the comments

let person = {
  "id": 12345,
  "name": "John Doe",
  "emergencyContacts": [{
      "name": "Jane Doe",
      "phone": "888-555-1212",
      "relationship": "spouse",
      "moreDetails": {
        "id": 12345,
        "phones": {},
        "home": "800-123-4567",
        "mobile": "877-123-1234"
      }
    },
    {
      "name": "Justin Doe",
      "phone": "877-123-1212",
      "relationship": "parent",
      "mobile": "877-123-1234"
    }
  ],
  "workContacts": [{
      "name": "Jane Doe",
      "phone": "888-555-1212",
      "relationship": "spouse",
      "moreworkDetails": {
        "id": 12345,
        "phones": {},
        "home": "800-123-4567",
        "mobile": "877-123-1236"
      }
    },
    {
      "name": "Justin Doe",
      "phone": "877-123-1212",
      "relationship": "parent",
      "mobile": "877-123-1235"
    }
  ]
}
let arrys = [];

//iterate the object 
function recursive(obj, key) {
  //iterate the object
  for (let keys in obj) {
    // check if the key name is same as the desired one
    if (keys === key) {
      // then push the value to an array
      arrys.push(obj[keys])

    } else {
      // if the value of a key is an array & if it is not empty
      if (Array.isArray(obj[keys]) && obj[keys].length > 0) {
        // iterate the array. in each iteration you will get the object
        // example emergencyContacts. In each iteration 
        // call the same function with new data 
        obj[keys].forEach(function(item) {
          // pass that object to the same function
          recursive(item, key)
        })

      }
      // if the value is an object then again call the same function 
      else if (typeof obj[keys] === 'object') {
        recursive(obj[keys], key)
      }
    }
  }
}

recursive(person, 'mobile');
//create object from the array
let newObj = {}
for (let m = 0; m < arrys.length; m++) {
  newObj[m] = arrys[m]

}
console.log(newObj)



回答2:

You don't need recursion. You can do it with while loop:

function searchFor(what, where) {
  const stack = [where];
  const result = [];
  while (stack.length) {
    const item = stack.pop();
    if (Array.isArray(item)) {
      item.forEach(el => stack.push(el));
      continue;
    }
    if (item && typeof item === "object")
      Object.entries(item).forEach(([key, value]) =>
        key === what ?
          result.push(value) :
          stack.push(value)
      )
  }
  return result;
}



回答3:

I generalized it a bit with code shamelessly stolen from my own lib: goodcore

I chose a recursice approach since I believe the code is much easier to analyze although it has more code.

let obj = {
    "id": 12345,
    "name": "John Doe",
    "emergencyContacts": [{
        "name": "Jane Doe",
        "phone": "888-555-1212",
        "relationship": "spouse",
        "moreDetails": {
          "id": 12345,
          "phones": {},
          "home": "800-123-4567",
          "mobile": "877-123-1234"
        }
      },
      {
        "name": "Justin Doe",
        "phone": "877-123-1212",
        "relationship": "parent",
        "mobile": "877-123-1234"
      }
    ],
    "workContacts": [{
        "name": "Jane Doe",
        "phone": "888-555-1212",
        "relationship": "spouse",
        "moreworkDetails": {
          "id": 12345,
          "phones": {},
          "home": "800-123-4567",
          "mobile": "877-123-1236"
        }
      },
      {
        "name": "Justin Doe",
        "phone": "877-123-1212",
        "relationship": "parent",
        "mobile": "877-123-1235"
      }
    ]
  };

function isObject(it) {
    return it !== null && typeof it === "object";
}
function isArray(it) {
    return Array.isArray ? Array.isArray(it) : Object.prototype.toString.call(it) === "[object Array]";
}
function forEach(target, fn) {
    if(isArray(target)) {
        target.forEach(fn);
    } else {
        Object.entries(target).forEach(([key, value]) => fn(value, key));
    }
}
function objReduce(obj, fn, acc) {
    let a = acc;
    forEach(obj, (value, key) => {
        if(isObject(value) || isArray(value)) {
            a = objReduce(value, fn, a);
        } 
        a = fn(a, value, key);
    });
    return a;
}

objReduce(obj, (acc, cur, key) => {
    if(key === "mobile") {
        acc.push(cur);
    }
    return acc;
}, []);

Basically what it does is that it creats an object reduce function that loops over all properties and runs an accumulator function on it much like Array.prototype.reduce does for arrays. It needed a bit of code since it handles arrays and objects arbitrarily.



回答4:

 returnValuesForAttribute = (attr) => {
    let mobile = {};
    let index = 0;
    Object.values(person).map(first_level => {
        if (Array.isArray(first_level)) {
            first_level.map(el => {
                if (Object.keys(el).includes(attr)) {
                    mobile[index] = el[attr];
                    index++;
                }
                Object.values(el).map(second_level => {
                    if (typeof second_level === 'object' && second_level[attr]) {
                        mobile[index] = second_level[attr];
                        index++;
                    }
                })
            })
        }
    });
    return mobile;
}
returnValuesForAttribute('mobile');

Output: {0: "877-123-1234", 1: "877-123-1234", 2: "877-123-1234", 3: "877-123-1234"}