0

Given the following three objects, what is an efficient way to return the first object that contains a key-value pair?

var obj = {
  item1: {
    name: 'apple',
    color: 'red'
  },
  item2: {
    name: 'blueberry',
    color: 'blue'
  },
  item3: {
    name: 'cherry',
    color: 'red'
  }
};

var obj2 = {
  collection: [
    {
      item1: {
        name: 'apple',
        color: 'red'
      },
      item2: {
        name: 'blueberry',
        color: 'blue'
      },
      item3: {
        name: 'cherry',
        color: 'red'
      }
    }
  ]
};

var obj3 = {
  items: [
    {
      item1: {
        name: 'apple',
        color: 'red'
      }
    },
    {
      item2: {
        name: 'blueberry',
        color: 'blue'
      },
    },
    {
      item3: {
        name: 'cherry',
        color: 'red'
      }
    }
  ]
};

I would like to get the same results for the following three statements:

getObject(obj, 'color', 'red');
getObject(obj2, 'color', 'red');
getObject(obj3, 'color', 'red');

Output:

{
  name: 'apple',
  color: 'red'
}

Here's what I have so far, but I think it's missing a closure somewhere since it breaks when the function calls itself:

function getObject(arg, key, val) {
  if (typeof arg!=='object') return null;
  switch (Object.prototype.toString.call(arg)) {
    case '[object Array]':
      for (var i=0; i<arg.length; ++i) {
        getObject(arg[i], key, val);
      }
      break;
    case '[object Object]':
      for (var i in arg) {
        if (arg.hasOwnProperty(i)) {
          if (typeof arg[i]==='object') {
            getObject(arg[i], key, val);
          } else {
            if (i===key && arg[i]===val) {
              return arg;
            }
          }
        }
      }
      break;
  }
}
thdoan
  • 18,421
  • 1
  • 62
  • 57
  • FYI, you can use [`Array.isArray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) to check if the object is an array. – Heretic Monkey Apr 10 '19 at 19:03
  • Possible duplicate of [How to find index of an object by key and value in an javascript array](https://stackoverflow.com/questions/11258077/how-to-find-index-of-an-object-by-key-and-value-in-an-javascript-array) – Heretic Monkey Apr 10 '19 at 19:10

4 Answers4

4

You could use this function, which will also search in more deeply nested data structures.

function getObject(obj, prop, value) {
    if (Object(obj) !== obj) return; // It is not an object
    if (obj[prop] === value) return obj; // Found it
    for (var key in obj) {
        var result = getObject(obj[key], prop, value);
        if (result) return result; // Found it
    }
}

var obj = {item1: {name: 'apple',color: 'red'},item2: {name: 'blueberry',color: 'blue'},item3: {name: 'cherry',color: 'red'}};
var obj2 = {collection: [{item1: {name: 'apple',color: 'red'},item2: {name: 'blueberry',color: 'blue'},item3: {name: 'cherry',color: 'red'}}]};
var obj3 = {items: [{item1: {name: 'apple',color: 'red'}},{item2: {name: 'blueberry',color: 'blue'},},{item3: {name: 'cherry',color: 'red'}}]};

console.log(getObject(obj, 'color', 'red'));
console.log(getObject(obj2, 'color', 'red'));
console.log(getObject(obj3, 'color', 'red'));
trincot
  • 317,000
  • 35
  • 244
  • 286
  • I actually like your answer the most and ended up using it, but unfortunately I accepted another answer before seeing your solution. Apologies. – thdoan Apr 10 '19 at 19:47
  • 1
    I guess better late than never. I found out you could actually unselect an accepted answer. Thanks again for this awesomely concise solution! – thdoan Apr 10 '19 at 19:52
1

You could use Array#some for a short cut, if the object is found.

function getObject(object, key, value) {
    var result;
    if (!object || typeof object !== 'object') return;
    if (object[key] === value) return object;
    Object.values(object).some(v => result = getObject(v, key, value));
    return result;
}

var obj = { item1: { name: 'apple', color: 'red' }, item2: { name: 'blueberry', color: 'blue' }, item3: { name: 'cherry', color: 'red' } },
    obj2 = { collection: [{ item1: { name: 'apple', color: 'red' }, item2: { name: 'blueberry', color: 'blue' }, item3: { name: 'cherry', color: 'red' } }] },
    obj3 = { items: [{ item1: { name: 'apple', color: 'red' } }, { item2: { name: 'blueberry', color: 'blue' } }, { item3: { name: 'cherry', color: 'red' } }] };

console.log(getObject(obj, 'color', 'red'));
console.log(getObject(obj2, 'color', 'red'));
console.log(getObject(obj3, 'color', 'red'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

Your approach within getObject is OK, however, as a general approach, you should transform the array for each all. You can use the function map in order to prepare the array for the finding process.

The function find "finds" the object according to a specific predicate.

This is assuming the indexes are related to the keys -> index i==0 -> items1, and so on.

var obj = {item1: {name: 'apple',color: 'red'},item2: {name: 'blueberry',color: 'blue'},item3: {name: 'cherry',color: 'red'}};
var obj2 = {collection: [{item1: {name: 'apple',color: 'red'},item2: {name: 'blueberry',color: 'blue'},item3: {name: 'cherry',color: 'red'}}]};
var obj3 = {items: [{item1: {name: 'apple',color: 'red'}},{item2: {name: 'blueberry',color: 'blue'},},{item3: {name: 'cherry',color: 'red'}}]};

let getObject = (o, key, value) => o.find(obj => obj[key] === value);

console.log(getObject(Object.values(obj), 'color', 'red'));
console.log(getObject(obj2.collection.map((o, i) => o[`item${i+1}`]), 'color', 'red'));
console.log(getObject(obj3.items.map((o, i) => o[`item${i+1}`]), 'color', 'red'));
.as-console-wrapper { min-height: 100%; }
Ele
  • 33,468
  • 7
  • 37
  • 75
  • Thanks for this, @Ele. While I admire the brevity of the solution, I won't be able to use this right now because (1) the code base is standarized on ES5, and (2) other developers who use this helper function will not know to `.map()` when appropriate. – thdoan Apr 10 '19 at 19:20
  • @thdoan no problem! – Ele Apr 10 '19 at 19:22
  • @thdoan, I just stumbled over this comment: *code base is standardized on ES5*. If ES5 is a requirement, then that should be clearly mentioned in the question. – trincot Apr 10 '19 at 19:41
0

You can call the function recursively to reach an object that hasn't any objects-members and filter it by key and value:

function getObject(obj, k, v) {
    for (var key in obj) {
        if (typeof obj[key] === 'object') {
            return getObject(obj[key], k, v);
        } else if (key === k && obj[key] === v) {
            return(obj);
        }
    }   
}

var obj = {
  item1: {
    name: 'apple',
    color: 'red'
  },
  item2: {
    name: 'blueberry',
    color: 'blue'
  },
  item3: {
    name: 'cherry',
    color: 'red'
  }
};

var obj2 = {
  collection: [
    {
      item1: {
        name: 'apple',
        color: 'red'
      },
      item2: {
        name: 'blueberry',
        color: 'blue'
      },
      item3: {
        name: 'cherry',
        color: 'red'
      }
    }
  ]
};

var obj3 = {
  items: [
    {
      item1: {
        name: 'apple',
        color: 'red'
      }
    },
    {
      item2: {
        name: 'blueberry',
        color: 'blue'
      },
    },
    {
      item3: {
        name: 'cherry',
        color: 'red'
      }
    }
  ]
};
console.log(getObject(obj, 'color', 'red'));
console.log(getObject(obj2, 'color', 'red'));
console.log(getObject(obj3, 'color', 'red'));
Banzay
  • 9,310
  • 2
  • 27
  • 46