I have an object:
myObj = {
attendent-0-id:"123",
attendent-0-name:"Bob Smith",
attendent-1-id:"1234",
attendent-1-name:"Alex Smith",
attendent-2-id:"123",
attendent-2-name:"Charlie Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
I need to create a loop that go through myObj and find all the id's and then compares them for duplicates. So in this case it would log an error because attendent-0-id is equal to attendent-2-id.
If I do find duplicates I need to set a flag to true;
I have tried a bunch of things and am just stuck at this point. Thanks for any help.
In your case you can go through myObj using Object.keys() via:
for (const key of Object.keys(obj))
use a plain object as a map to store the previous values of the ids:
const map = {};
use a regex pattern to make sure only the specific ids are evaluated:
const pattern = /^attendent-\d+-id$/;
and then with the help of the map, log the error on duplicate ids:
if (value in map) {
console.error(`${map[value]} is equal to ${key}, which is ${value}`);
}
Example:
const myObj = {
'attendent-0-id': "123",
'attendent-0-name': "Bob Smith",
'attendent-1-id': "1234",
'attendent-1-name': "Alex Smith",
'attendent-2-id': "123",
'attendent-2-name': "Charlie Smith",
'attendent-maxGuest': 1,
'attendent-party-name': "",
};
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
console.error(`${map[value]} is equal to ${key}, which is ${value}`);
} else {
map[value] = key
}
}
}
}
errorOnDuplicateIds(myObj);
const ids = []; // keep track of found ids
Object.keys(myObj).forEach(key => { // iterate over all properties of myObj
// check if property name is in format "attendent-" *some number* "-id"
if (/^attendent-\d+-id$/.test(key)) {
// check if the id has already been found
if (ids.findIndex(id => id === myObj[key]) !== -1) {
console.log('error');
} else {
ids.push(myObj[key]);
}
}
});
You can use Object.entries and a Map (keyed by value) for this:
var myObj = {"attendent-0-id":"123","attendent-0-name":"Bob Smith","attendent-1-id":"1234","attendent-1-name":"Alex Smith","attendent-2-id":"123","attendent-2-name":"Charlie Smith","attendent-maxGuest":1, "attendent-party-name":""};
var dupes = [...Object.entries(myObj).reduce(
(map, [key,val]) => map.set(val, (map.get(val) || []).concat(key)),
new Map
).values()].filter(keys => keys.length > 1);
console.log(dupes);
This solution does not give any particular meaning to the format of the keys.
Having said that, your object structure looks suspicious of bad design: you should not have enumerations in your object keys. For that you should use arrays.
Object.values(myObj) will create an array of all values and then you can use any way to find duplicate elements in that array.
var myValues = Object.values(myObj); //This will create an array of all values
var uniq = myValues.map((val) => {
return {count: 1, val: val}
}).reduce((a, b) => {
a[b.val] = (a[b.val] || 0) + b.count
return a
}, {});
var duplicates = Object.keys(uniq).filter((a) => uniq[a] > 1)
if (duplicates.length) {
return true;
} else {
return false;
}
My first advice would be to redefine your object to something more flexible.
let myObject = {
attendants : [
{
id: "123",
name: "Bob Smith"
},
{
id: "456",
name: "Alex Smith"
},
{
id: "768",
name: "Charlie Smith"
},
],
maxGuest: 1,
partyName: ""
};
This will allow you to iterate the attendants.
for (var attendant in myObject.attendants){
doSomething(attendant.id, attendant.name);
}
You can also sort the attendant:
// Sort by id
myObject.attendants.sort(function(left, right){
return left.value - right.value;
});
// Sort by name
myObject.attendants.sort(function (left, right){
var leftName = left.name.toLowerCase();
var rightName = right.name.toLowerCase();
if (leftName < rightName) return -1;
if (leftName > rightName) return 1;
return 0;
});
Now, lets assume you don't have a choice. Then it gets complicated.
You need to create (or modify an existent) a sort algorithm so it can use keys that are generated as:
myObject[`attendent-${index}-id`]
myObject[`attendent-${index}-name`]
and keep the pair
Related
Problem
I would like to have the below two JSON combined together using the ID and have the expected result as mentioned below. I have tried a few solutions that were available but none worked for my use case. Any suggestions will be great !!
Tried to do:
How to merge two json object values by id with plain Javascript (ES6)
Code
var json1 = [
{
"id":"A123",
"cost":"5020.67",
"fruitName":"grapes"
},
{
"id":"A456",
"cost":"341.30",
"fruitName":"apple"
},
{
"id":"A789",
"cost":"3423.04",
"fruitName":"banana"
}
];
var json2 = [
{
"id":"A123",
"quantity":"7"
},
{
"id":"A789",
"quantity":"10"
},
{
"id":"ABCD",
"quantity":"22"
}
];
Below is the code I tried:
var finalResult = [...[json1, json2].reduce((m, a) => (a.forEach(o => m.has(o.id) && Object.assign(m.get(o.id), o) || m.set(o.id, o)), m), new Map).values()];
Expected result:
[
{
"id":"A123",
"cost":"5020.67",
"fruitName":"grapes",
"quantity":"7"
},
{
"id":"A456",
"cost":"341.30",
"fruitName":"apple"
},
{
"id":"A789",
"cost":"3423.04",
"fruitName":"banana",
"quantity":"10"
},
{
"id":"ABCD",
"quantity":"22"
}
]
You can accomplish this fairly easily without getting too fancy. Here's the algorithm:
Put the items from json1 into an object by id, so that you can look them up quickly.
For each item in json2: If it already exists, merge it with the existing item. Else, add it to objectsById.
Convert objectsById back to an array. I've used Object.values, but you can also do this easily with a loop.
var json1 = [
{
"id":"A123",
"cost":"5020.67",
"fruitName":"grapes"
}, {
"id":"A456",
"cost":"341.30",
"fruitName":"apple"
}, {
"id":"A789",
"cost":"3423.04",
"fruitName":"banana"
}
];
var json2 = [
{
"id":"A123",
"quantity":"7"
}, {
"id":"A789",
"quantity":"10"
}
];
const objectsById = {};
// Store json1 objects by id.
for (const obj1 of json1) {
objectsById[obj1.id] = obj1;
}
for (const obj2 of json2) {
const id = obj2.id;
if (objectsById[id]) {
// Object already exists, need to merge.
// Using lodash's merge because it works for deep properties, unlike object.assign.
objectsById[id] = _.merge(objectsById[id], obj2)
} else {
// Object doesn't exist in merged, add it.
objectsById[id] = obj2;
}
}
// All objects have been merged or added. Convert our map to an array.
const mergedArray = Object.values(objectsById);
I think a few steps are being skipped in your reduce function. And it was a little difficult to read because so many steps are being combined in one.
One critical piece that your function does not account for is that when you add 2 numerical strings together, it concats the strings.
const stringTotal = "5020.67" + "3423.04" // result will be "5020.673423.04"
The following functions should give you the result you are looking for.
// calculating the total cost
// default values handles cases where there is no obj in array 2 with the same id as the obj compared in array1
const calcualteStringTotal = (value1 = 0, value2 = 0) => {
const total = parseFloat(value1) + parseFloat(value2)
return `${total}`
}
const calculateTotalById = (array1, array2) => {
const result = []
// looping through initial array
array1.forEach(outterJSON => {
// placeholder json obj - helpful in case we have multiple json in array2 with the same id
let combinedJSON = outterJSON;
// looping through second array
array2.forEach(innerJSON => {
// checking ids
if(innerJSON.id === combinedJSON.id) {
// calls our helper function to calculate cost
const updatedCost = calcualteStringTotal(innerJSON.cost, outterJSON.cost)
// updating other properties
combinedJSON = {
...outterJSON,
...innerJSON,
cost: updatedCost
}
}
})
result.push(combinedJSON)
})
return result
}
const combinedResult = calculateTotalById(json1, json2)
I figured that by using reduce I could make it work.
var finalResult = [...[json1, json2].reduce((m, a) => (a.forEach(o => m.has(o.id) && Object.assign(m.get(o.id), o) || m.set(o.id, o)), m), new Map).values()];
I have a JS Object that may look like one of the following:
// Example #1:
var data = {
product: {
value1: 'v1',
value2: 2
}
}
// Example #2:
var data = {
order: {
value1: 1
}
}
// Example #3:
var data = {
value1: 1
}
What I'd like to achieve:
var inputName = 'product[value1]';
var data = {
product: {
value1: 'v1',
value2: 2
}
}
var value = something(data, inputName); // should return v1
inputName and data can change, I can have any of the above data Objects with an inputName like 'product[value2]', 'order[value1]', 'value1'.
My guess is to use regex and get both attribute names. Is the a better way?
you can use underscore js _.each to iterate over the object like
_.each(data ,function(product){
console.log(product.value);
});
see the link: http://underscorejs.org/#each
you can also use for each loop.
Also you can perform filter like below:
_.filter(data, function(product){
return product.value;
});
One other way is to create a dictionary that directly caters to your search.
You can flatten your multi-level key value pair to create a dictionary that you can use readily. The below flatten function (taken from here) creates a dictionary like:
{
"product.value1": "v1",
"product.value2": 2
}
You can then just query it using dictionary["product.value1"]
This flatten function can be altered to format the keys like product[value1] if you wish so.
var data = {
product: {
value1: 'v1',
value2: 2
}
}
var myDictionary = flatten(data);
console.log(myDictionary);
console.log(myDictionary["product.value1"]);
console.log(myDictionary["product.something else"]);
function flatten(obj, opt_out, opt_paths) {
var out = opt_out || {};
var paths = opt_paths || [];
return Object.getOwnPropertyNames(obj).reduce(function(out, key) {
paths.push(key);
if (typeof obj[key] === 'object') {
flatten(obj[key], out, paths);
} else {
out[paths.join('.')] = obj[key];
}
paths.pop();
return out;
}, out)
}
If you know possible attribute names, then I would define an array with possible attribute names, then iterate over them checking if there is a field with this name
const names = [
'product',
'order'
];
function findValue(data){
if(data.value1) return data.value1;
for(let name in names){
if(data[name].value1) return data[name].value1;
}
}
Explanation
If you want to give your function a string like 'product[value1]' as argument you need to get all attribute values that you need to query for your result value. I did it with query.replace(/(\[)|(\])/g, ' ')split(' '). The returning array you need to check for empty strings and remove them. I did it with filter.
After that you can simply use reduce on the returned array to get on each iteration the new value. In the last iteration you have your result.
Code
function getDataValue(obj, query) {
var attributes = getAttributeNames(query)
return attributes.reduce(function(value, current) {
return value[current]
}, obj)
}
function getAttributeNames(query) {
return query.replace(/(\[)|(\])/g, ' ')
.split(' ')
.filter(function(string) {
return string.length > 0
})
}
Example
var dataOne = {
product: {
value1: 'v1',
value2: 2
}
}
var dataTwo = {
product: {
subProduct: {
value1: 'v2'
}
}
}
console.log(getDataValue(dataOne, 'product[value1]'))
console.log(getDataValue(dataTwo, 'product[subProduct][value1]'))
function getDataValue(obj, query) {
var attributes = getAttributeNames(query)
return attributes.reduce(function(value, current) {
return value[current]
}, obj)
}
function getAttributeNames(query) {
return query.replace(/(\[)|(\])/g, ' ')
.split(' ')
.filter(function(string) {
return string.length > 0
})
}
I want to reverse the mapping of an object (which might have duplicate values). Example:
const city2country = {
'Amsterdam': 'Netherlands',
'Rotterdam': 'Netherlands',
'Paris': 'France'
};
reverseMapping(city2country) Should output:
{
'Netherlands': ['Amsterdam', 'Rotterdam'],
'France': ['Paris']
}
I've come up with the following, naive solution:
const reverseMapping = (obj) => {
const reversed = {};
Object.keys(obj).forEach((key) => {
reversed[obj[key]] = reversed[obj[key]] || [];
reversed[obj[key]].push(key);
});
return reversed;
};
But I'm pretty sure there is a neater, shorter way, preferably prototyped so I could simply do:
const country2cities = city2country.reverse();
You could use Object.assign, while respecting the given array of the inserted values.
const city2country = { Amsterdam: 'Netherlands', Rotterdam: 'Netherlands', Paris: 'France' };
const reverseMapping = o => Object.keys(o).reduce((r, k) =>
Object.assign(r, { [o[k]]: (r[o[k]] || []).concat(k) }), {})
console.log(reverseMapping(city2country));
There is no such built-in function in JavaScript. Your code looks fine, but given that there are so many edge cases here that could wrong, I'd suggesting using invertBy from lodash, which does exactly what you describe.
Example
var object = { 'a': 1, 'b': 2, 'c': 1 };
_.invertBy(object);
// => { '1': ['a', 'c'], '2': ['b'] }
You can use something like this to get raid of duplicates first :
function removeDuplicates(arr, key) {
if (!(arr instanceof Array) || key && typeof key !== 'string') {
return false;
}
if (key && typeof key === 'string') {
return arr.filter((obj, index, arr) => {
return arr.map(mapObj => mapObj[key]).indexOf(obj[key]) === index;
});
} else {
return arr.filter(function(item, index, arr) {
return arr.indexOf(item) == index;
});
}
}
and then use this to make it reverse :
function reverseMapping(obj){
var ret = {};
for(var key in obj){
ret[obj[key]] = key;
}
return ret;
}
You could try getting an array of values and an array of keys from the current object, and setup a new object to hold the result. Then, as you loop through the array of values -
if the object already has this value as the key, like Netherlands, you create a new array, fetch the already existing value (ex: Rotterdam), and add this and the new value (Amsterdam) to the array, and set up this array as the new value for the Netherlands key.
if the current value doesn't exist in the object, set it up as a new string, ex: France is the key and Paris is the value.
Code -
const city2country = {
'Amsterdam': 'Netherlands',
'Rotterdam': 'Netherlands',
'Paris': 'France',
};
function reverseMapping(obj) {
let values = Object.values(obj);
let keys = Object.keys(obj);
let result = {}
values.forEach((value, index) => {
if(!result.hasOwnProperty(value)) {
// create new entry
result[value] = keys[index];
}
else {
// duplicate property, create array
let temp = [];
// get first value
temp.push(result[value]);
// add second value
temp.push(keys[index]);
// set value
result[value] = temp;
}
});
console.log(result);
return result;
}
reverseMapping(city2country)
The benefit here is - it adjusts to the structure of your current object - Netherlands being the repeated values, gets an array as it's value in the new object, while France gets a string value Paris as it's property. Of course, it should be very easy to change this.
Note - Object.values() might not be supported across older browsers.
You could use reduce to save the declaration line reduce.
Abusing && to check if the map[object[key]] is defined first before using Array.concat.
It's shorter, but is it simpler? Probably not, but a bit of fun ;)
const reverseMapping = (object) =>
Object.keys(object).reduce((map, key) => {
map[object[key]] = map[object[key]] && map[object[key]].concat(key) || [key]
return map;
}, {});
#Nina Scholz answer works well for this exact question. :thumbsup:
But if you don't need to keep both values for the Netherlands key ("Netherlands": ["Amsterdam", "Rotterdam"]), then this is a little bit shorter and simpler to read:
const city2country = { Amsterdam: 'Netherlands', Rotterdam: 'Netherlands', Paris: 'France' };
console.log(
Object.entries(city2country).reduce((obj, item) => (obj[item[1]] = item[0]) && obj, {})
);
// outputs `{Netherlands: "Rotterdam", France: "Paris"}`
Lets say there are two objects but one object has property different from the other. Is there a way to figure out what properties match?
for example:
var objectOne = {
boy: "jack",
girl: "jill"
}
var objectTwo = {
boy: "john",
girl: "mary",
dog: "mo"
}
edit: It should tell me boy and girl property name are found in both the objects.
var in_both = [];
for (var key in objectOne) { // simply iterate over the keys in the first object
if (Object.hasOwnProperty.call(objectTwo, key)) { // and check if the key is in the other object, too
in_both.push(key);
}
}
C.f. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
Now, if you want to test if the values are the same, too, than simply add more code to the condition/body of the inner if.
Using Object.keys
Object.keys(objectOne).filter(k => Object.hasOwnProperty.call(objectTwo, k))
You can use Object.keys and use Array.prototype.reduce to loop through once and list out the common keys - see demo below:
var objectOne={boy:"jack",girl:"jill"};
var objectTwo={boy:"john",girl:"mary",dog:"mo"};
var result = Object.keys(objectOne).reduce(function(p,c){
if(c in objectTwo)
p.push(c);
return p;
},[]);
console.log(result);
If you want to find out which keys match given two objects, you could loop through all of the keys of the objects using a for... in loop. In my function, it will loop through the keys and return an array of all of the matching keys in the two objects.
let objectOne = {
boy: "jack",
girl: "jill"
}
let objectTwo = {
boy: "john",
girl: "mary",
dog: "mo"
}
function matchingKeys (obj1, obj2) {
let matches = [];
let key1, key2;
for (key1 in obj1) {
for (key2 in obj2) {
if ( key1 === key2) {
matches.push(key1);
}
}
}
return matches
}
const result = matchingKeys(objectOne, objectTwo);
console.log(result)
Try this on for size:
function compare(obj1, obj2) {
// get the list of keys for the first object
var keys = Object.keys(obj1);
var result = [];
// check all from the keys in the first object
// if it exists in the second object, add it to the result
for (var i = 0; i < keys.length; i++) {
if (keys[i] in obj2) {
result.push([keys[i]])
}
}
return result;
}
This isn't better than some solutions here, but I thought I'd share:
function objectHas(obj, predicate) {
return JSON.stringify(obj) === JSON.stringify({ ...obj, ...predicate })
}
With an array of objects in a form like this:
[
{
1429={
{
8766={...},
8483={...},
7345={...}
}
}
},
{
9041={...}
}
]
how could i get back an array like this?:
[1429, 9041]
If the array of objects would be in another structure this code would work:
var obj = {
"5": "some",
"8": "thing"
};
var keys = $.map(obj, function (value, key) {
return key;
});
console.log(keys);
That would return [5, 8]. But in my example it just would return the indexes [0,1]
Even if I wouldn't know the depth of the object - is it possible to get the values on that level? I dont need the indexes, I need those values. I couldn't find anything about it so far. Any tips for me maybe?
P.S.: I know that i could work out something with these keys and a loop, but I'm just asking for a simplier way to do it.
Regards
you are looking for the keys in a json object, you can get them this way:
Object.keys(obj);
for the object example:
var obj = {
"5": "some",
"8": "thing"
};
you will get:
["5","8"]
for an array of object of this type:
var arrayObject = [{},{},{}];
you can use a map and get the keys:
var keys = arrayObject.map(function(k){
return Object.keys(k);
});
keys is an array of arrays of keys. Example, for the following object (similar to your data structure):
var l= [
{
1429:{
8766: "test",
8483:"test",
7345: "test"
}
},
{
9041: "test"
}
];
you will get:
[["1429"],["9041"]]
apply concat and you will get what you are looking for. Here how to apply concat in the case of multiple arrays.
var arrayOfKeys = [].concat.apply([], keys);
now you will get:
["1429","9041"];
In your specific case you could use
var keys = [];
root.forEach(function(v) { keys = keys.concat(Object.keys(v)); });
If instead you have a tree of arrays and you want the keys of all other objects instead (but not recursing into objects) then a simple recursive function would do it:
function topKeys(x) {
if (x && x.constructor === Array) {
var result = [];
x.forEach(function(item) {
result = result.concat(topKeys(item));
});
return result;
} else if (typeof x === "object") {
return Object.keys(x);
} else {
return [];
}
}