I'm getting wrong output in Javascript Function? - javascript

I have function that looks through an array of objects (first argument) and returns an array of all objects that have matching name and value pairs (second argument). Each name and value pair of the source object has to be present in the object from the collection if it is to be included in the returned array.
function whatIsInAName(collection, source) {
let keyArr = Object.keys(source);
for (let i = 0; i < keyArr.length; i++) {
var arr = collection.filter(function(item) {
return item.hasOwnProperty(keyArr[i])
})
}
return arr.filter(function(item) {
for (let g = 0; g < keyArr.length; g++) {
return item[keyArr[g]] === source[keyArr[g]];
}
});
}
console.log(whatIsInAName([{
"a": 1,
"b": 2,
"c": 3
}], {
"a": 1,
"b": 9999,
"c": 3
}));
I should give an empty array [ ].
but It is giving [{"a": 1, "b": 2, "c": 3}]

You had an error in your second filter.
In for statement you were returning comparision result of first item from that statement and not checking if whole arr is equal.
In code below I have changed for statement to return false when items doesn't match. Then at the end there is returned true becouse there wasn't found any item that doesn't match with the other arr.
function whatIsInAName(collection, source) {
let keyArr = Object.keys(source);
for(let i = 0; i < keyArr.length; i++){
var arr = collection.filter(item => item.hasOwnProperty(keyArr[i]));
}
return arr.filter(item => {
for(let g = 0; g < keyArr.length; g++) {
if (item[keyArr[g]] !== source[keyArr[g]]) {
return false;
}
}
return true;
});
}
console.log(
whatIsInAName(
[{"a": 1, "b": 2, "c": 3}, {"a": 1, "b": 9999, "c": 3}],
{"a": 1, "b": 9999, "c": 3}
)
);

What is the first loop for? The arr created there is overwritten in every iteration of the loop. So the final arr will be the array of items which at least contain the last property in Object.keys(). You can apply your filter directly on the collections parameter.
function whatIsInAName(collection, source) {
let keys = Object.keys(source);
return collection.filter(item => keys.every(k => item[k] === source[k]));
}
console.log(whatIsInAName([
{"a": 1, "b": 9999, "c": 3 },
{"a": 2, "b": 9999, "c": 3 },
{"a": 1, "b": 2, "c": 3 },
{"a": 1, "b": 9999, "c": 3, "d": 4 }
],
{"a": 1, "b": 9999, "c": 3}));
Keep in mind that the equality check === will only work for primitive values but not for objects or arrays. Also this approach will accept additional properties in the items of the collection, that are not present in the source item.

Related

Create duplicate array of object

Say I have an array of object::
const banana = [{"a":"ann","b":"bann","det":[{"c":"cat","d":"dog"},{"c":"conn","d":"donn"}]}, {"a":"auu","b":"buu","det":[{"c":"camel","d":"damel"},{"c":"coww","d":"doww"}]}]
I want to transform this array of object in this form::
const banana = [{"a":"ann","b":"bann","c":"cat","d":"dog"}, {"a":"ann","b":"bann","c":"conn","d":"donn"}, {"a":"auu","b":"buu","c":"camel","d":"damel"}, {"a":"auu","b":"buu","c":"coww","d":"doww"}]
As you can see array of object inside array of object have merged and duplicated.
I tried as:
const apple = []
for(let i = 0; i<banana.length;i++){
for(let j = 0;j<banana[i].det.length;j++{
apple.push(banana[i].det[j])
}
}
console.log(apple)
**OUTPUT: [{c: "cat", d: "dog"},{c: "conn", d: "donn"},{c: "camel", d: "damel"},{c: "coww", d: "doww"}]**
But I'm looking for the O/P as:
[{"a":"ann","b":"bann","c":"cat","d":"dog"}, {"a":"ann","b":"bann","c":"conn","d":"donn"},
{"a":"auu","b":"buu","c":"camel","d":"damel"}, {"a":"auu","b":"buu","c":"coww","d":"doww"}]
But I'm unable to form logic. I'm still trying but if i could get some guidance that would be really helpful.
**EDIT:**So I've come up with an idea using spread operator:
let enamel = {}
for(let i = 0; i<banana.length;i++){
for(let j = 0;j<banana[i].det.length;j++){
employee = {
...banana[j],
...banana[i].det[j]
};
}
}
It gives the output as:
console.log(enamel)
{a: "auu", b: "buu", det: Array(2), c: "coww", d: "doww"}
But I want to have all the objects in an array as previously stated.
You can use this logic, which copies over initial object, adds extra properties, drops the det array, and flatten the result
function extras(obj) {
// create a copy of the current context (initial obj)
// and add all properties from the extra object
obj = Object.assign({}, this, obj);
// but delete the `det` from the copy
delete obj.det;
// and return the object
return obj;
}
// per each array object ...
banana
.map(
// and per each det ...
obj => obj.det.map(extras, obj)
)
// flatten the final array of objects
.flat();
You just have to extract a and b from object in banana. I have used destructuring to extract it.
const banana = [{ "a": "ann", "b": "bann", "det": [{ "c": "cat", "d": "dog" }, { "c": "conn", "d": "donn" }] }, { "a": "auu", "b": "buu", "det": [{ "c": "camel", "d": "damel" }, { "c": "coww", "d": "doww" }] }]
const apple = []
for (let i = 0; i < banana.length; i++) {
for (let j = 0; j < banana[i].det.length; j++) {
const {a,b} = banana[i];
const {c,d} = banana[i].det[j];
apple.push({a,b,c,d});
}
}
console.log(apple)
You can do this:
const banana = [
{
"a": "ann",
"b": "bann",
"det": [{ "c": "cat", "d": "dog" }, { "c": "conn", "d": "donn" }]
},
{
"a": "auu",
"b": "buu",
"det": [
{ "c": "camel", "d": "damel" },
{ "c": "coww", "d": "doww" }
]
}
]
const result = [];
banana.forEach( b =>{
b.det.forEach(d =>{
result.push({
a: b.a,
b: b.b,
c: d.c,
d: d.d
});
});
});
console.log(result);
Try this
const banana = [{"a":"ann","b":"bann","det":[{"c":"cat","d":"dog"},{"c":"conn","d":"donn"}]}, {"a":"auu","b":"buu","det":[{"c":"camel","d":"damel"},{"c":"coww","d":"doww"}]}]
const output = []
for (const { a, b, det } of banana) {
for (const animal of det) {
output.push({a, b, ...animal })
}
}
console.log(output)
I think you want to do it like this in case you want to avoid manually take a and b and other property except 'det' properties
function getResult(banana) {
const answer = [];
banana.forEach(element => {
const arrayData = element['det'];
delete element['det'];
// remove the 'del' property temporarily
arrayData.forEach(subElement => {
answer.push({
...element, // basically spread operator to make copy of all properties each time
...subElement
});
});
// restore the 'del' proprty
element['det'] = arrayData;
});
console.log("the result is : ", answer);
return answer;
}

Object destructing using ES6

I have this nested object that contains also an array.
result:{
"a": [{ "b": { "c": 1,
"d": 2,
"e": 3
},
"f": 0
}]
}
How can I destructure this object using ES6 if I need the value of d?
Object destructuring notation is just like object literal notation, just on the other side of the equals sign. So you write exactly what you'd write to only create the structure necessary for d, and put your d variable/constant name there.
It looks like you're showing us the contents of your object (e.g., it has a property called result), so:
const obj = {
result: {
"a": [{
"b": {
"c": 1,
"d": 2,
"e": 3
},
"f": 0
}]
}
};
const {
result: {
"a": [{
"b": {
d
}
}]
}
} = obj;
console.log(d);
I'm not saying I'd use destructuring here. In fact, I probably wouldn't. But you can. :-)
If result was the variable containing the object, just remove that layer:
const obj = {
"a": [{
"b": {
"c": 1,
"d": 2,
"e": 3
},
"f": 0
}]
};
const {
"a": [{
"b": {
d
}
}]
} = obj;
console.log(d);
Of course, that's taking your question at face value that you want the d from the first entry in a. You can generalize it to get entry n like this (I'm back to assuming result is part of the object):
const obj = {
result: {
"a": [
{
"b": {
"c": 1,
"d": 2,
"e": 3
},
"f": 0
},
{
"b": {
"c": 1,
"d": 4, // ***
"e": 3
},
"f": 0
}
]
}
};
const n = 1; // Which entry in `a` to get
const {
result: {
"a": {
[n]: {
"b": {
d
}
}
}
}
} = obj;
console.log(d);
I'm using object destructuring for a rather than array destructuring, with a computed property name. I can do that because arrays are objects.
Array destructuring notation is just like array literal notation, too. :-)
You want the value of d?
result.a[0].b.d ?

How to fill in missing keys in an Array of Objects?

I have an Array of Objects which should all have the same keys, but some of the keys are missing. I would like to fill in the missing keys with a generic value.
I am looking for a simple way to do that (natively or via a library), the code below I use now works, bit looks to my untrained eyes quite heavy and I am sure I reinvented the tedious way to do something while there is a simple one.
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
]
// get the list of all keys
var allkeys = []
arr.forEach((objInArr) => {
allkeys = allkeys.concat(Object.keys(objInArr))
})
// check all arr entries for missing keys
arr.forEach((objInArr, i) => {
allkeys.forEach((key) => {
if (objInArr[key] === undefined) {
// the generic value, in this case 0
arr[i][key] = 0
}
})
})
console.log(arr)
Here is a version using property spread in object literals, although this will have very limited browser support:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
]
// Create an object with all the keys in it
// This will return one object containing all keys the items
let obj = arr.reduce((res, item) => ({...res, ...item}));
// Get those keys as an array
let keys = Object.keys(obj);
// Create an object with all keys set to the default value (0)
let def = keys.reduce((result, key) => {
result[key] = 0
return result;
}, {});
// Use object destrucuring to replace all default values with the ones we have
let result = arr.map((item) => ({...def, ...item}));
// Log result
console.log(result);
Your version is fine, although I would probably avoid all those array concat calls by just building up an object (or Set) with the keys. It's also a bit less clunky with for-of:
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
];
// Get all the keys
const keyObj = Object.create(null);
for (const entry of arr) {
for (const key of Object.keys(entry)) {
keyObj[key] = true;
}
}
const allkeys = Object.keys(keyObj);
// Check all arr entries for missing keys
for (const entry of arr) {
for (const key of allkeys) {
if (entry[key] === undefined) { // ***I'd change this
entry[key] = 0;
}
}
}
console.log(arr);
.as-console-wrapper {
max-height: 100% !important;
}
Re *** I'd change this: Note that there's a difference between a property that exists and has the value undefined and a property that doesn't exist at all. Your code is treating them as the same thing. Of course, if you know they won't have the value undefined (for instance, because of the API you're getting them from)...
You can use Object.assign to merge each element with an object holding default key-values:
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
];
var defaultObj = arr.reduce((m, o) => (Object.keys(o).forEach(key => m[key] = 0), m), {});
arr = arr.map(e => Object.assign({}, defaultObj, e));
console.log(arr);

To find object whereabouts in array of objects

function whatIsInAName(collection, source) {
var result = [];
var arr1 = Object.keys(source);
console.log(arr1);
for (var i = 0; i < collection.length; i++) {
for (var j = 0; j < arr1.length; j++) {
if (collection[i].hasOwnProperty(arr1[j]) === false) { //Check 1 if false go to next object in collection
break;
} else if (collection[i].hasOwnProperty(arr1[j])) {
console.log(source[arr1[j]], collection[i][arr1[j]])
if (source[arr1[j]] !== collection[i][arr1[j]]) { //Check 2 if value is not equal break loop and goto next object in collection
break;
}
continue; // if both check passes go for next property of source to check in object;
}
result.push(collection[i]); //if all values are present and checked in object push it in result array.
}
}
return result;
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
], {
a: 1,
b: 2
}
));
I couldn't figure out the problem in my logic. I try to debug it even but can't find what the hell is a problem with logic.The program is to make a function that looks through an array of objects (first argument) and returns an array of all objects that have matching property and value pairs (second argument) Kindly help me over, please.
whatIsInAName([{ "a": 1, "b": 2 }, { "a": 1 }, { "a": 1, "b": 2, "c": 2 }], { "a": 1, "b": 2 })
should return
[{ "a": 1, "b": 2 }, { "a": 1, "b": 2, "c": 2 }].
and
whatIsInAName([{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 2 }], { "a": 1 })
should return
[{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 2 }].
Well you are complicating it too much with these two for loops, you can do it better using Array built-in methods.
This is how you can do it using .filter() and .some() methods:
function whatIsInAName(collection, source) {
var result = [];
var arr1 = Object.keys(source);
console.log(arr1);
result = collection.filter(function(obj){
return !arr1.some(function(k){
return !obj[k] || obj[k] !== source[k];
});
});
return result;
}
console.log(whatIsInAName([{ a: 1, b: 2 }, { a: 1 }, { a: 1, b: 2, c: 2 }], { a: 1, b: 2 }));
Problem with your code :
What you are doing currently is that if all the if statements pass, and loops do not break anywhere, you put continue again at the last item of arr1. So, it goes to check next iteration, does not find it, and goes to next iteration in the collection loop. While doing this, it does not push any item to result.
Solution:
In your code: You should use continue like this.
if(j !==(arr1.length - 1))
continue;
This gives your code an opportunity to push item to result array.
Try this function
function whatIsInAName(arr, sources){
return arr.filter((item) => {
for(source in sources){
if(!item[source] || item[source] !== sources[source]){
return false;
}
}
return true;
})
}
I think you are over complicating things. A simple .filter() will do. Inside that .filter() you can check to see if every value for every key in source matches with the corresponding key in collection by using the handy, built-in .every() method.
function whatIsInAName(collection, source) {
var sourceKeys = Object.keys(source);
return collection.filter(function (coll) {
// if you want to return only exact matches, just add the test to make sure same # of keys, and all keys match w/ values.
// (Object.keys(coll).length === sourceKeys.length) && sourceKeys.every(...)
return sourceKeys.every(function (key) {
return coll[key] === source[key];
});
});
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
], {
a: 1,
b: 2
}
));
Alternatively, with ES6 Syntax:
function whatIsInAName(collection, source) {
return collection.filter(coll => Object.keys(source).every(key => coll[key] === source[key]));
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
], {
a: 1,
b: 2
}
));
Your issue is that you are hitting your continue instead of falling out of the loop. I updated your code here:
function whatIsInAName(collection, source) {
var result = [];
var arr1 = Object.keys(source);
console.log(arr1);
for (var i = 0; i < collection.length; i++) {
for (var j = 0; j < arr1.length; j++) {
if (!collection[i].hasOwnProperty(arr1[j])) { //Check 1 if false go to next object in collection
break;
} else if (collection[i].hasOwnProperty(arr1[j])) {
console.log(source[arr1[j]], collection[i][arr1[j]])
if (source[arr1[j]] !== collection[i][arr1[j]]) { //Check 2 if value is not equal break loop and goto next object in collection
break;
}else if(j < arr1.length - 1){
continue; // if both check passes go for next property of source to check in object;
}
}
result.push(collection[i]);
}
}
return result;
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
],
{
a: 1,
b: 2
}
)); // result is [Object {...}, Object {...}] which objects are {a: 1, b: 2}, {a: 1, b: 2, c: 2}

How to best "dynamically" make javascript have an conditional to find matches based on multiple possible key value pairs? Looking for a solution

How do I most efficiently structure a "conditions" object to find matches within a list of objects (consisting of key value pairs) and add them to a list of "matches?
I have a the following testList that contains multiple objects:
var testList = {
"A": "SUBA1",
"B": "SUBB2",
"C": "SUBC5",
},
{
"A": "SUBA2",
"B": "SUBB3",
"C": "SUBC1",
}
...
]
Right now my matchCondition with one condition is simple focused on one key and value:
var matchCondition = {"key": "A", val:"SUBA1"};
I want to shove any individual object in testList that matches my "matchCondition" into the "matchedList".
var matchedList = [];
So I do this now using "findMatches" function:
function findMatches(matchCondition, testList) {
var matchedList = [];
for(var i = 0; i < testList.length; i++) {
if(testList[matchCondition[key]] == testList[i][matchCondition[val]]) {
matchedList.push(testList[i]);
}
}
return matchedList;
}
My problem is, what if I want to match using multiple conditions something where for example "A" could be equal to "SUBA1" or "SUBA2", AND "B" is "SUBB2", AND "C" is "SUBB5":
Maybe the object could look like this?
var matchCondition = {
"A": ["SUBA1", "SUBA2"],
"B": ["SUBB2"],
"C": ["SUBC5"]
}
I am not sure what is the best way I can update my findMatches function to be more robust... or how do I best structure my "matchCondition" to be able to support multiple key values I want to match?
A proposal with some array methods like Array.prototype.filter() and Array.prototype.every() and Object.keys().
var testList = [{ "A": "SUBA1", "B": "SUBB2", "C": "SUBC5", }, { "A": "SUBA2", "B": "SUBB3", "C": "SUBC1", }],
matchCondition = { "A": ["SUBA1", "SUBA2"], "B": ["SUBB2"], "C": ["SUBC5"] };
function filter(data, condition) {
return data.filter(function (a) {
return Object.keys(condition).every(function (k) {
return ~condition[k].indexOf(a[k]);
});
});
}
document.write('<pre>' + JSON.stringify(filter(testList, matchCondition), 0, 4) + '</pre>');
You can iterate the mach condition object hasOwnProperty() and check its value meets the requirement.
var testList = [{
"A": "SUBA1",
"B": "SUBB2",
"C": "SUBC5",
}, {
"A": "SUBA2",
"B": "SUBB3",
"C": "SUBC1",
}];
var matchCondition = {
"A": ["SUBA1", "SUBA2"],
"B": ["SUBB2"],
"C": ["SUBC5"]
}
function findMatches(testList, matchCondition) {
return testList.filter(function(obj){
for (var key in matchCondition) {
if (matchCondition.hasOwnProperty(key) && obj.hasOwnProperty(key)) {
if( matchCondition[key].indexOf(obj[key]) == -1){
return false;
}
}else{
return false;
}
}
return true
});
}
snippet.log(JSON.stringify(findMatches(testList, matchCondition)))
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
A good read Javascript iterate object
It is not clear what you are expecting as a result, but here is a possible ES6 solution; based on your multiple condition matchCondition example.
'use strict';
var testList = [{
"A": "SUBA1",
"B": "SUBB2",
"C": "SUBC5",
}, {
"A": "SUBA2",
"B": "SUBB3",
"C": "SUBC1",
}];
var matchCondition = {
"A": ["SUBA1", "SUBA2"],
"B": ["SUBB2"],
"C": ["SUBC5"]
};
var matchedList = [];
for (let item of testList) {
let x = Object.create(null);
for (let key of Object.keys(matchCondition)) {
let val = item[key];
if (val) {
for (let sub of matchCondition[key]) {
if (sub === val) {
x[key] = sub;
break;
}
}
}
}
matchedList.push(x);
}
document.getElementById('out').textContent = JSON.stringify(matchedList, null, 2);
console.log(matchedList);
<pre id="out"></pre>
Or similar in ES5
'use strict';
var testList = [{
"A": "SUBA1",
"B": "SUBB2",
"C": "SUBC5",
}, {
"A": "SUBA2",
"B": "SUBB3",
"C": "SUBC1",
}];
var matchCondition = {
"A": ["SUBA1", "SUBA2"],
"B": ["SUBB2"],
"C": ["SUBC5"]
};
var matchedList = testList.reduce(function(acc, item) {
var x = Object.create(null);
Object.keys(matchCondition).forEach(function(key) {
var val = item[key];
if (val) {
matchCondition[key].some(function(sub) {
if (sub === val) {
x[key] = sub;
return true;
}
});
}
});
acc.push(x);
return acc;
}, []);
document.getElementById('out').textContent = JSON.stringify(matchedList, null, 2);
console.log(matchedList);
<pre id="out"></pre>
These are not your only possibilities, but you need to be clearer about your expectations and it would be good to see what you have tried, and where you are having a problem.

Categories