Javascript compare two JSON arrays and return key of the unmatched value - javascript

I have two JSON arrays, would like to know the key which don't match. I don't need the value.
Example:
livetable: [
{ id: 1, name: "Sandra" },
{ id: 2, name: "John" },
],
backupTable: [
{ id: 1, name: "Sandra" },
{ id: 2, name: "Peter" },
],
I can get the key/value pair which is diffrent with this Lodash script:
difference = _.differenceWith(livetable,backupTable,_.isEqual)
But I would just need the key, in this example "name" for "id: 2" is not matching, so I would need to get the "name" key to new array/variable.
(Using VUE CLI)
EDIT: Added example of current code output.
var livetable = [{"id": 1, "name": "Sandra", "id": 2, "name": "John"}]
var backupTable = [{"id": 1, "name": "Sandra", "id": 2, "name": "Peter"}]
console.log(_.differenceWith(backupTable,livetable,_.isEqual))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
This will output the key:value pair, but I would just need the key which is diffrent.

I think I understand what you're trying to do. There are some unknowns though, like what should happen if there is a missing record in the second data set?
This solution assumes each table of data has the same amount of records and the records have the same IDs.
// define data
const livetable = [
{ id: 1, name: "Sandra" },
{ id: 2, name: "John" }
]
const backupTable = [
{ id: 1, name: "Sandra" },
{ id: 2, name: "Peter" }
]
const getDifferentRecordsByID = (sourceRecords, compareRecords) => {
// simple utility function to return a record object matching by ID
const findComparisionRecord = id => compareRecords.find(compareRecord => compareRecord.id === id)
// using the utility function, we can filter out any mismatching records by comparing name
return sourceRecords
.filter(sourceRecord => sourceRecord.name !== findComparisionRecord(sourceRecord.id).name)
// then map over all the records and just pull out the ID
.map(record => record.id)
}
console.log(getDifferentRecordsByID(livetable, backupTable)) // [2]

Here is working VUE code for my problem.
Function returns [ "name" ], which is exactly what I need.
data() {
return {
livetable: [{ id: 1, name: "Sandra" },{ id: 2, name: "John" }],
backupTable: [{ id: 1, name: "Sandra" },{ id: 2, name: "Peter" }],
difColumns: null,
};
},
methods: {
test3() {
let resultArray = []
this.livetable.forEach((array1, index) => {
const array2 = this.backupTable[index];
resultArray.push(this._.reduce(array1, (result, value, key) => this._.isEqual(value, array2[key]) ? result : result.concat(key), []))
});
this.difColumns = resultArray[0]
}
},

Related

Filter out array using another array of objects

I am having two arrays
const selected = [];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
const result = []
I need to compare these two arrays and the result should only have the single entry instead of duplicates. In the above example result should have the following output.
Also items in the selected should be taken into consideration and should be in the beginning of the result
result = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
Also when the input is following
const selected = [ {id:5, name: "xyz" }];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
result = [[
{ id: 5, name: "xyz" },
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
Also when the input is following
const selected = [ {id:1, name: "abc" }, {id:4, name: "lmn" }];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
result = [[
{ id: 1, name: "abc" },
{ id: 4, name: "lmn" }
{ id: 2, name: "def" }
];
Note the comparison should be made using name field
Code that I tried
const res = [...(selected || [])].filter((s) =>
current.find((c) => s.name === c.name)
);
Sandbox: https://codesandbox.io/s/nervous-shannon-j1vn5k?file=/src/index.js:115-206
You could get all items and filter the array by checking the name with a Set.
const
filterBy = (key, s = new Set) => o => !s.has(o[key]) && s.add(o[key]),
selected = [{ id: 1, name: "abc" }, { id: 1, name: "lmn" }],
current = [{ id: 1, name: "abc" }, { id: 2, name: "def" }],
result = [...selected, ...current].filter(filterBy('name'));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Loop through selected, and if there is no object in current with a name that matches the name of the object in the current iteration push it into current.
const selected=[{id:1,name:"abc"},{id:6,name:"def"},{id:4,name:"lmn"}];
const current=[{id:1,name:"abc"},{id:2,name:"def"}];
for (const sel of selected) {
const found = current.find(cur => cur.name === sel.name);
if (!found) current.push(sel);
}
console.log(current);
This is a good use for .reduce, avoids multiple loops/finds and doesn't need filtering with side-effects.
const selected = [ {id:1, name: "abc" }, {id:4, name: "lmn" }];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
const result = Object.values(
[...selected, ...current].reduce((obj, item) => {
obj[item.name] = obj[item.name] || item;
return obj;
}, {})
)
console.log(result);

How do I check that the correct objects are being returned via a function (expect function returns [Function Anonymous])?

I have a function:
const sort =
(pets,attribute) =>
_(pets)
.filter(pets=> _.get(pets, attribute) !== null)
.groupBy(attribute)
.value()
Some data:
const pets= [{
id: 1,
name: 'snowy',
},
{
id: 2,
name: 'quacky',
},
{
id: 3,
name: 'snowy',
age: 5,
},
{
id: null,
name: null,
age: null
}
]
const attribute = 'name'
I am currently trying to write some Jest unit tests for this, that tests if the function returns the correct resultant object after being sorted based off an attribute.
The result of:
sort(pets,attribute) is something like this:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Is there a way I can do a expect to match the two objects snowy and quacky here?
The thing I want to test for is that the objects are being correctly grouped by the key.
I've tried using something like
const res = sort(users,key)
expect(res).toEqual(
expect.arrayContaining([
expect.objectContaining({'snowy' : [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5 } ]},
expect.objectContaining({'quacky' : [ { id: 2, name: 'quacky' } ]}))
])
)
which doesn't seem to work, the received output seems to output:
Expected: ArrayContaining [ObjectContaining {"snowy": [{"id": 1, "name": "snowy"}, {"age": 5, "id": 3, "name": "snowy"}]}]
Received: [Function anonymous]
I am unsure what the best method to test this kind of function is either so advice on that would be appreciated.
If this is what your arrangeBy() returns:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Then you can just do:
const expected = {
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
const res = arrangeBy(users,key)
expect(res).toEqual(expected)
But looking at your Error message I guess you have something else mixed up. In the beginning you listed the implementation of a sort function which seems to not be used in the test. Where is arrangeBy coming from now.
Please provide more code examples.

Map over two arrays to see if one property matches, then push specific info into the first array

New to javascript and trying to learn! I am trying to map through two array of objects, and if a certain property matches, pull in specific information into the first array.
let result;
let arrNames = [{
id: 10
name: "A"
}, {
id: 11,
name: "B"
}, {
id: 12,
name: "C"
}, }, {
id: 13,
name: "A"
}, {
id: 14,
name: "B"
}]
let arrInfo = [{
name: "A",
info: "AAA"
}, {
name: "B",
info: "BBB"
}, {
name: "C",
info: "CCC"
}]
If arrNames.name == arrInfo.name, I would like push info into the names array.
Desired result:
let arrNames = [{
id: 10
name: "A",
info: "AAA"
}, {
id: 11,
name: "B",
info: "BBB"
}, {
id: 12,
name: "C",
info: "CCC"
}, }, {
id: 13,
name: "A",
info: "AAA"
}, {
id: 14,
name: "B",
info: "BBB"
}]
What I've tried:
const res = arrInfo.map((el, index) => {
if(el.name == arrNames[index].name)
arrNames.push(el.info)
}
^ This obviously doesn't work -- but I'm wondering if extend or push would be appropriate here.
Thanks in advance for your help (apologies that this is probably a dupe).
Convert arrInfo to a Map, with the name as the key. Now map arrNames and add the info you get from arrInfoMap using the name. Use object spread to combine both objects:
const arrNames = [{"id":10,"name":"A"},{"id":11,"name":"B"},{"id":12,"name":"C"},{"id":13,"name":"A"},{"id":14,"name":"B"}]
const arrInfo = [{"name":"A","info":"AAA"},{"name":"B","info":"BBB"},{"name":"C","info":"CCC"}]
const arrInfoMap = new Map(arrInfo.map(o => [o.name, o]))
const result = arrNames.map(o => ({ ...o, ...arrInfoMap.get(o.name) }))
console.log(result)
You can do something like this:
let arrNames = [
{
id: 10,
name: 'A'
},
{
id: 11,
name: 'B'
},
{
id: 12,
name: 'C'
},
{
id: 13,
name: 'A'
},
{
id: 14,
name: 'B'
}
];
let arrInfo = [
{
name: 'A',
info: 'AAA'
},
{
name: 'B',
info: 'BBB'
},
{
name: 'C',
info: 'CCC'
}
];
// do this
const result = arrNames.map((item) => {
const newItem = item; // here we define a new object that is the same as your object that is currently looped up to in your arrNames array
// loop your second array over this currently looped to object, seeing if the name matches
arrInfo.forEach((item2) => {
if (item.name === item2.name) {
newItem.info = item2.info; // if they do set a new property for your new object called info as the info from item 2 of this arrInfo array
}
});
// return this new object whether or not there was a match for the name property
return newItem;
});
console.log(result);
So the thing with your map method is that you need to remember to return something at the end of your callback function. You are simply pushing to an array, which is like using .map as a forEach. Map makes one array into another array of the same length. Here you are trying to make a new array where the array element being looped over will have an extra info property should it match your second array arrInfo's name.
So you what you can do is a forEach inside your map to check if they match, if so add a new property to your arrayNames element and return that as the new element for your newly created array. Hope that helped, please ask for clarifications if you need in the comments.

Javascript map over array of obj with another array to get different key value

So I am not sure why I having such a difficult time with this, but I have an array of ids that I am trying to use to map over an array of objects to find the corresponding id but return the value from a different key.
i.e.:
arr=[13, 1, 16]
arrObj= [{
id: 1,
name: "cat"
}, {
id: 10,
name: "tiger",
}, {
id: 3,
name: "dog",
}, {
id: 16,
name: "bear",
}, {
id: 8,
name: "fish",
}, {
id: 13,
name: "goat",
}]
and I want it to return:
["goat", "cat", "bear"]
I have a nested map function that does this but returns undefined for the objects that do not have a corresponding ID. I could filter out the undefineds from the returned array, but it seems that there is a cleaner/more efficient way to do this.
What is the cleanest way to achieve this?
You could use Array#map and search with Array#find for the corresponding object. Then take name as return value.
var arr = [13, 1, 16],
arrObj = [{ id: 1, name: "cat" }, { id: 10, name: "tiger" }, { id: 3, name: "dog" }, { id: 16, name: "bear" }, { id: 8, name: "fish" }, { id: 13, name: "goat" }],
result = arr.map(id => arrObj.find(o => o.id === id).name);
console.log(result);
For a lots of data, you could take a Map and build it by mapping key value pairs and then map the result of the map.
var arr = [13, 1, 16],
arrObj = [{ id: 1, name: "cat" }, { id: 10, name: "tiger" }, { id: 3, name: "dog" }, { id: 16, name: "bear" }, { id: 8, name: "fish" }, { id: 13, name: "goat" }],
result = arr.map(
Map.prototype.get,
new Map(arrObj.map(({ id, name }) => [id, name]))
);
console.log(result);
Try this:
var arr=[13, 1, 16];
var arrObj= [{
id: 1,
name: "cat"
}, {
id: 10,
name: "tiger",
}, {
id: 3,
name: "dog",
}, {
id: 16,
name: "bear",
}, {
id: 8,
name: "fish",
}, {
id: 13,
name: "goat",
}];
var result = arr.map(id => arrObj.find(x => x.id == id)).map(x => x.name)
console.log(result);
// ["goat", "cat", "bear"]
.map() (from MDN web docs):
method creates a new array with the results of calling a provided
function on every element in the calling array.
.find() (from MDN web docs):
method returns the value of the first element in the array that
satisfies the provided testing function. Otherwise undefined is
returned.
There have been a lot of good answers already, however i feel the need to push for newer syntax as it is greater to work with in the long run.
const getNamesBasedOnIds = (arr, listOfIds) => {
return arr.reduce(((sum, element) =>
[...sum, ...(listOfIds.includes(element.id) ? [element.name] : [])]),
[]);
}
const animals = getNamesBasedOnIds(arrObj, [13,1,16]);
Using Array.filter, then Array.map requires you to run through the Array max twice.
With Array.reduce you can add elements to the sum if they exists in listOfIds, then after running through the arrObj once you have the result
[...sum, ...(listOfIds.includes(element.id) ? [element.name] : [])] is pretty slow, but its more for showing the use of spear operators
The above is equivalent to
sum.concat(listOfIds.includes(element.id) ? element.name : [])]
The fastest way, is to use Array.push
if(listOfIds.includes(element.id)){
sum.push(element.name);
}
return sum;
The correct way is to not nest any loops:
reduce your array of objects to a map of 'id-values' idValueMap
map through your array of 'ids' idArr using the idValueMap to get
the corresponding values for each id in constant time.
Thought Process
Understand that the biggest problem is the data itself. Don't let insufficient data types or structures force you you have a bad solution/code. Transform the data such to what you need so that you can create a proper solution.
Basically imagine if the objArr was just a Map that you could use to look up the values for an id.... then the solution is straight forward. So let's make that happen and then the rest falls into place. Data Data Data is what I always say :)
Rule of thumb NEVER run a loop inside of a loop if you can help it. FYI: filter, find, map, indexOf .... are all loops internally, do not nest them unless you absolutely must.
This solution is by far the most performant. This way you are not running O(n^2) (very BAD), you are instead running O(n) (very GOOD):
const idArr = [ 13, 1, 16 ];
const objArr= [
{
id: 1,
name: "cat"
},
{
id: 10,
name: "tiger",
},
{
id: 3,
name: "dog",
},
{
id: 16,
name: "bear",
},
{
id: 8,
name: "fish",
},
{
id: 13,
name: "goat",
}
];
const idValueMap = objArr.reduce((acc, { id, name }) => ({ ...acc, [id]: name }), {});
let output = idArr.map((id) => idValueMap[id]);
console.log(output);
You can first use .filter to filter and then use .map to output the desired properties
here
var arr = [13, 1, 16],
arrObj = [{
id: 1,
name: "cat"
}, {
id: 10,
name: "tiger",
}, {
id: 3,
name: "dog",
}, {
id: 16,
name: "bear",
}, {
id: 8,
name: "fish",
}, {
id: 13,
name: "goat",
}];
var res = arrObj.filter(o => arr.indexOf(o.id) >= 0);
console.log(res.map(o => o['name']))

JavaScript Object Array: Removing objects with duplicate properties

I have an array of objects:
[
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
I'd like to strip out objects with duplicate Ids, leaving an array that would look like this:
[
{ id: 1, name: "Bob" },
{ id: 2, name: "Daryl" }
]
I don't care which objects are left, as long as each ID is unique. Anything in Underscore, maybe, that would do this?
Edit: This is not the same as the duplicate listed below; I'm not trying to filter duplicate OBJECTS, but objects that contain identical IDs. I've done this using Underscore - I'll post the answer shortly.
You can use reduce and some to good effect here:
var out = arr.reduce(function (p, c) {
// if the next object's id is not found in the output array
// push the object into the output array
if (!p.some(function (el) { return el.id === c.id; })) p.push(c);
return p;
}, []);
DEMO
the es6 way
function removeDuplicates(myArr, prop) {
return myArr.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos
})
}
Test it
let a =[
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
console.log( removeDuplicates( a, 'id' ) )
//output [
{ id: 1, name: "Bob" },
{ id: 2, name: "Daryl" }
]
If you use underscore, you can use the _uniq method
var data = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
_.uniq(data, function(d){ return d.ID });
Produces a duplicate-free version of the array, using === to test object equality. In particular only the first occurence of each value is kept. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iteratee function.
Source: http://underscorejs.org/#uniq
Can use es6 Map collection mix with reduce
const items = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
const uniqItems = [...items.reduce((itemsMap, item) =>
itemsMap.has(item.id) ? itemsMap : itemsMap.set(item.id, item)
, new Map()).values()]
console.log(uniqItems);
Using findIndex should be the simplest solution.
array.filter((elem, index, arr) => arr.findIndex(e => e.id === elem.id) === index)
You can simply filter the array, but you'll need an index of existing IDs that you've already used...
var ids = [];
var ar = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
];
ar = ar.filter(function(o) {
if (ids.indexOf(o.id) !== -1) return false;
ids.push(o.id);
return true;
});
console.log(ar);
Here's some documentation on filter()...
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Categories

Resources