I have two JSON arrays like this. I want to merge their keyvalue pairs. They are some items which are common in both while they are some items which are uncommon.
var jsonData1 = [
{
firstName: "Sam",
age: "10"
},
{
firstName: "John",
age: "11"
},
{
firstName: "Jack",
age: "12"
},
{
firstName: "Pam",
age: "13"
},
{
firstName: "Tom",
age: "14"
},
{
firstName: "Mitch",
age: "15"
}
];
var jsonData2 = [
{
firstName: "Sam",
city: "London"
},
{
firstName: "John",
city: "New York"
},
{
firstName: "Jack",
city: "Paris"
},
{
firstName: "Pam",
city: "Moscow"
},
{
firstName: "Roger",
city: "Shanghai"
},
{
firstName: "Don",
city: "Jakarta"
}
];
As you can there are some firstName in 1st array which does not have city in 2nd array. Again there are some firstName in 2nd array which does not have city in 1st array.
I need to club these 2 array into one array, in case a firstName does not have age or city it will be assigned '' (blank string).
The new array will have 3 fields, there are some items which will be having values in 2 fields. They have a one field as blank string.
I want to do this using Vanilla JS, I do not want to use Jquery, Lodash and Underscore.
There are a number of ways to approach this, however one option would be to base this around the Array#reduce() method as follows:
const jsonData1=[{firstName:"Sam",age:"10"},{firstName:"John",age:"11"},{firstName:"Jack",age:"12"},{firstName:"Pam",age:"13"},{firstName:"Tom",age:"14"},{firstName:"Mitch",age:"15"}];
const jsonData2=[{firstName:"Sam",city:"London"},{firstName:"John",city:"New York"},{firstName:"Jack",city:"Paris"},{firstName:"Pam",city:"Moscow"},{firstName:"Roger",city:"Shanghai"},{firstName:"Don",city:"Jakarta"}];
/*
Use Array#concat() to combine both input arrays before performing
the merge. This will ensure that all items in both arrays are
processed and accounted for during the merge (which is important in
situations where the input arrays differ in length).
Object.values() is used to transform the dictionary resulting from
reduce() to an array
*/
var result = Object.values(
[].concat(jsonData1, jsonData2
).reduce((dict, item) => {
/*
We use Array#reduce is an intermediate step to construct a dictionary
which maps each unique "item.firstName" to a corresponding object
that contains the merged (or yet to be merged) data for this first
name
*/
var value = dict[item.firstName] || {}
/*
Use Object.assign() to merge existing data for "item.firstName" with
current item being processed. Also pass default values as first
argument to ensure all three key values are present in merge result
*/
value = Object.assign({ firstName : '', age : '', city : ''} , value, item)
/*
Update the dictionary with merged data
*/
dict[item.firstName] = value
return dict
}, {}))
console.log(result);
One possible approach:
const jsonData1=[{firstName:"Sam",age:"10"},{firstName:"John",age:"11"},{firstName:"Jack",age:"12"},{firstName:"Pam",age:"13"},{firstName:"Tom",age:"14"},{firstName:"Mitch",age:"15"}];
const jsonData2=[{firstName:"Sam",city:"London"},{firstName:"John",city:"New York"},{firstName:"Jack",city:"Paris"},{firstName:"Pam",city:"Moscow"},{firstName:"Roger",city:"Shanghai"},{firstName:"Don",city:"Jakarta"}];
const result = [].concat(jsonData1, jsonData2).reduce((acc, ele) => {
const existing = acc.find(x => x.firstName === ele.firstName);
if(!existing) return acc.concat({firstName: ele.firstName, city: ele.city || '', age: ele.age || ''});
Object.keys(ele).forEach(x => existing[x] = ele[x]);
return acc;
},[])
console.log(result);
I realize you've already accepted an answer but I figured I could provide an alternative, be it better or worse.
var jsonData1 = [{firstName: "Sam",age: "10"},{firstName: "John",age: "11"},{firstName: "Jack",age: "12"},{firstName: "Pam",age: "13"},{firstName: "Tom",age: "14"},{firstName: "Mitch",age: "15"}];
var jsonData2 = [{firstName: "Sam",city: "London"},{firstName: "John",city: "New York"},{firstName: "Jack",city: "Paris"},{firstName: "Pam",city: "Moscow"},{firstName: "Roger",city: "Shanghai"},{firstName: "Don",city: "Jakarta"}];
var defaults = {firstName: "", age: "", city: ""};
var data = [ ...jsonData1, ...jsonData2 ];
var names = [ ...new Set(data.map(i=>i.firstName)) ];
var res = names.map(n => data
.reduce((acc, jd) => jd.firstName === n ? {...acc, ...jd} : acc, defaults)
);
console.log(res);
var data combines the two arrays of data into one using spread syntax (array literals).
var names creates an array of unique names using Set.
map() iterates over the list of names, creating a single, merged object for each. That merge is done using reduce() and spread syntax (object literals).
Related
I'm trying to prepare an array into a json object to send to an API.
I'm struggling to figure out how to manipulate my array into the right shape.
My array looks something like this.
data: [
["Lisa", "Heinz", "1993-04-15" ],
["Bob", "Dylan", "1998-09-12"],
["Cabbage", "Man", "1990-01-11"],
["", "", ""]
]
I'd like it to be a json object looking like this:
{person:[{"name":"Lisa","last_name":"Heinz","dob":"1993-04-15"},{"name":"Bob","last_name":"Dylan","dob":"1998-09-12"},{"name":"Cabbage","last_name":"Man","dob":"1990-01-11"},{"name":"","last_name":"","dob":""}],"object_id":259,"test":"bob","attribute":"bob123"}
Currently I do this:
let json = {}
for (let person of formData) {
const identifier = `person${formData.indexOf(person)}`;
json[identifier] = {
name: person[0],
last_name: person[1],
dob: person[2]
}
}
json.object_id = "259";
json.wp_test = "bob";
json.attribute = "bob123";
Which outputs something like this:
{"person0":{"name":"Lisa","last_name":"Heinz","dob":"1993-04-15"},"person1":{"name":"Bob","last_name":"Dylan","dob":"1998-09-12"},"person2":{"name":"Cabbage","last_name":"Man","dob":"1990-01-11"},"person3":{"name":"","last_name":"","dob":""},"object_id":259,"wp_test":"bob","attribute":"bob123"}
I've tried a variety of things to get the right shape - what's an easily understandable way to get there?
It's just a matter of matching the correct keys/indexes.
var data = [
["Lisa", "Heinz", "1993-04-15"],
["Bob", "Dylan", "1998-09-12"],
["Cabbage", "Man", "1990-01-11"],
["", "", ""]
]
var persons = data.reduce(function(agg, item) {
agg.push({
name: item[0],
last_name: item[1],
dob: item[2],
})
return agg;
}, [])
var final = {
person: persons,
object_id: 259,
wp_test: 'bob',
attribute: 'bob123',
}
console.log(final)
.as-console-wrapper {max-height: 100% !important}
You can simply achieve this by iterating the input array with the help of Array.map() method.
Live Demo :
const data = [
["Lisa", "Heinz", "1993-04-15" ],
["Bob", "Dylan", "1998-09-12"],
["Cabbage", "Man", "1990-01-11"],
["", "", ""]
];
const jsonObj = {};
jsonObj.person = data.map(arr => ({ name: arr[0], last_name: arr[1], dob: arr[2] }));
jsonObj.object_id = "259";
jsonObj.wp_test = "bob";
jsonObj.attribute = "bob123";
console.log(jsonObj);
I want to define a function to find index of a JSON object in an array. The JSON object is dynamic.Here object keys/attributes are not constant in JSON.
How to find index of matched object (all key& values) in array?
For example:
let obj={name:'Bill', phone:'8562456871', email:'bill#email.com'};
let arrayObj=[{street:'wardcircle', city:'Brentwood'},{name:'wan',email:'wan#test.com' },{name:'bill', phone:'8562456871', email:'bill#email.com'}];
let indx=getIndex(obj,arrayObj); // expected result is 2
I have defined function like this but it is not working for all dynamic attribute & values:
getIndex(obj,arrayObj){
Object.keys(obj),forEach((key,index)=>{
return arrayObject.findIndex(x=>x[key]==obj[key]);// Here I am unable to add AND condition for other key& values.
});
}
Put the .findIndex first, and inside it, check that .every one of the Object.keys matches.
Note that your current object has name: 'Bill' but the array has name: 'bill' - the values should match, case sensitivity matters (unless you want to ignore it, in which case you'll have to call toLowerCase() on both values first).
let obj = {
name: 'bill',
phone: '8562456871',
email: 'bill#email.com'
};
let arrayObj = [{
street: 'wardcircle',
city: 'Brentwood'
}, {
name: 'wan',
email: 'wan#test.com'
}, {
name: 'bill',
phone: '8562456871',
email: 'bill#email.com'
}];
const getIndex = (findObj, array) => (
array.findIndex(obj => (
Object.entries(findObj).every(([key, val]) => obj[key] === val)
))
);
console.log(getIndex(obj, arrayObj));
If you also want to make sure that the found object doesn't have any properties not in the findObj, check that the number of keys on both are the same too:
let obj = {
name: 'bill',
phone: '8562456871',
email: 'bill#email.com'
};
let arrayObj = [{
street: 'wardcircle',
city: 'Brentwood'
}, {
name: 'wan',
email: 'wan#test.com'
}, {
name: 'bill',
phone: '8562456871',
email: 'bill#email.com'
}];
const getIndex = (findObj, array) => (
array.findIndex(obj => (
Object.keys(obj).length === Object.keys(findObj).length &&
Object.entries(findObj).every(([key, val]) => obj[key] === val)
))
);
console.log(getIndex(obj, arrayObj));
I have seen some questions that might look similar but none is the solution in my case. I want to regroup and recreate my array the way that it is arranged or grouped based on one of my values(age). I want to have all data of the same "age" in one place. So here is my sample array:
[
{
"age": 15,
"person": {
name: 'John',
hobby: 'ski'
},
},
{
"age": 23,
"person": {
name: 'Suzi',
hobby: 'golf'
},
},
{
"age": 23,
"person": {
name: 'Joe',
hobby: 'books'
}
},{
"age": 25,
"person": {
name: 'Rosi',
hobby: 'books'
}
},{
"age": 15,
"person": {
name: 'Gary',
hobby: 'books'
}
},
{
"age": 23,
"person": {
name: 'Kane',
hobby: 'books'
}
}
]
And I need to have an array that kind of have age as a key and person as value, so each key could have multiple values meaning the value will kind of be an array itself.
I have read this and this questions and many more but they were not exactly the same.
I feel like I need to use reduce to count duplicate ages and then filter it based on that but how do I get the values of those ages?
EIDT:
Sorry for not being clear:
This is what I need:
{
23: [
{ name: 'Suzi', hoby: 'golf' },
{ name: 'Joe', hobby: 'books'}
],
15: [
{ name: 'Gary', hobby: 'books' }
] ,
.
.
.
}
You're actually going to want to reduce, not filter. Filtering an Array means to remove elements and place the kept elements into a new container. Reducing an array means to transform it into a single value in a new container. Mapping an array means to transform every value in place to a new container. Since you want to change how the data is represented that's a Reduction, from one form to another more condensed form.
Assume your Array of values is stored in let people = [...]
let peopleByAge = people.reduce(function (accumulator, value, index, array){
// The first time through accumulator is the passed extra Object after this function
// See the MDN for Array.prototype.reduce() for more information
if (accumulator[value.age] == undefined){
accumulator[value.age] = [];
}
accumulator[value.age].push(value);
return accumulator
}, {})
console.log(peopleByAge) // { 23: [{ age: 23, name: ..., hobby: ...}, ...], 13: [...], ...}
You can find the MDN article for Array#reduce() here
Thanks to #RobertMennell who patiently answered me and I voted as answer. But I just wanted to write my version which MDN had a great example of. It is a longer version assuming the people is the array name:
const groupedByvalue = 'age';
const groupedArray = people;
const groupBy = (peopleArray, value) => {
return peopleArray.reduce((acc, obj) => {
const key = obj[value];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
console.log(groupBy(groupedArray,groupedByvalue));
Update:
More polished using ternary operator:
const groupedByvalue = 'age';
const groupedArray = people;
const groupBy = (peopleArray, value) => {
return peopleArray.reduce((acc, obj) => {
const key = obj[value];
(!acc[key]) ? (acc[key] = []) : (acc[key].push(obj))
return acc;
}, {});
}
console.log(groupBy(groupedArray,groupedByvalue));
I'm learning JS. Supposing I have the below array of objects:
var family = [
{
name: "Mike",
age: 10
},
{
name: "Matt"
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
];
Notice that Nancy is showing up twice (changing only the age). Supposing I want to output only unique names. How do I output the above array of objects, without duplicates? ES6 answers more than welcome.
Related (couldn't find a good way for usage on objects):
Remove Duplicates from JavaScript Array
Easiest way to find duplicate values in a JavaScript array
EDIT Here's what I tried. It works well with strings but I can't figure how to make it work with objects:
family.reduce((a, b) => {
if (a.indexOf(b) < 0 ) {
a.push(b);
}
return a;
},[]);
You could use a Set in combination with Array#map and a spread operator ... in a single line.
Map returns an array with all names, which are going into the set initializer and then all values of the set are returned in an array.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = [...new Set(family.map(a => a.name))];
console.log(unique);
For filtering and return only unique names, you can use Array#filter with Set.
var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
unique = family.filter((set => f => !set.has(f.name) && set.add(f.name))(new Set));
console.log(unique);
The Solution
Store occurrences of name external to the loop in an object, and filter if there's been a previous occurrence.
https://jsfiddle.net/nputptbb/2/
var occurrences = {}
var filteredFamily = family.filter(function(x) {
if (occurrences[x.name]) {
return false;
}
occurrences[x.name] = true;
return true;
})
you can also generalize this solution to a function
function filterByProperty(array, propertyName) {
var occurrences = {}
return array.filter(function(x) {
var property = x[propertyName]
if (occurrences[property]) {
return false;
}
occurrences[property]] = true;
return true;
})
}
and use it like
var filteredFamily = filterByProperty(family, 'name')
Explanation
Don't compare objects using indexOf, which only uses the === operator between objects. The reason why your current answer doesn't work is because === in JS does not compare the objects deeply, but instead compares the references. What I mean by that you can see in the following code:
var a = { x: 1 }
var b = { x: 1 }
console.log(a === b) // false
console.log(a === a) // true
Equality will tell you if you found the same exact object, but not if you found an object with the same contents.
In this case, you can compare your object on name since it should be a unique key. So obj.name === obj.name instead of obj === obj. Moreover another problem with your code that affects its runtime and not its function is that you use an indexOf inside of your reduce. indexOf is O(n), which makes the complexity of your algorithm O(n^2). Thus, it's better to use an object, which has O(1) lookup.
This will work fine.
const result = [1, 2, 2, 3, 3, 3, 3].reduce((x, y) => x.includes(y) ? x : [...x, y], []);
console.log(result);
With the code you mentioned, you can try:
family.filter((item, index, array) => {
return array.map((mapItem) => mapItem['name']).indexOf(item['name']) === index
})
Or you can have a generic function to make it work for other array of objects as well:
function printUniqueResults (arrayOfObj, key) {
return arrayOfObj.filter((item, index, array) => {
return array.map((mapItem) => mapItem[key]).indexOf(item[key]) === index
})
}
and then just use printUniqueResults(family, 'name')
(FIDDLE)
I just thought of 2 simple ways for Lodash users
Given this array:
let family = [
{
name: "Mike",
age: 10
},
{
name: "Matt",
age: 13
},
{
name: "Nancy",
age: 15
},
{
name: "Adam",
age: 22
},
{
name: "Jenny",
age: 85
},
{
name: "Nancy",
age: 2
},
{
name: "Carl",
age: 40
}
]
1. Find duplicates:
let duplicatesArr = _.difference(family, _.uniqBy(family, 'name'), 'name')
// duplicatesArr:
// [{
// name: "Nancy",
// age: 2
// }]
2 Find if there are duplicates, for validation purpose:
let uniqArr = _.uniqBy(family, 'name')
if (uniqArr.length === family.length) {
// No duplicates
}
if (uniqArr.length !== family.length) {
// Has duplicates
}
Since most of the answers won't have a good performance, i thought i share my take on this:
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(i => {
uniqueObj[i.id] = i;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
We're leveraging the fact that keys are unique within objects. That means the last duplication item inside the first array, will win over its predecessors. If we'd want to change that, we could flip the array before iterating over it.
Also we're not bound to use only one property of our object for identifying duplications.
const arrayWithDuplicateData = [{ id: 5, name: 'Facebook'}, { id: 3, name: 'Twitter' }, { id: 5, name: 'Facebook' }];
const uniqueObj = {};
arrayWithDuplicateData.forEach(item => {
uniqueObj[`${item.id}_${item.name}`] = item;
});
const arrayWithoutDuplicates = Object.values(uniqueObj);
Or we could simply add a check, if the uniqueObj already holds a key and if yes, not overwrite it.
Overall this way is not very costly in terms of performance and served me well so far.
I would probably set up some kind of object. Since you've said ECMAScript 6, you have access to Set, but since you want to compare values on your objects, it will take a little more work than that.
An example might look something like this (removed namespace pattern for clarity):
var setOfValues = new Set();
var items = [];
function add(item, valueGetter) {
var value = valueGetter(item);
if (setOfValues.has(value))
return;
setOfValues.add(value);
items.push(item);
}
function addMany(items, valueGetter) {
items.forEach(item => add(item, valueGetter));
}
Use it like this:
var family = [
...
];
addMany(family, item => item.name);
// items will now contain the unique items
Explanation: you need to pull a value from each object as it's added and decide if it has already been added yet, based on the value you get. It requires a value getter, which is a function that given an item, returns a value (item => item.name). Then, you only add items whose values haven't already been seen.
A class implementation:
// Prevents duplicate objects from being added
class ObjectSet {
constructor(key) {
this.key = key;
this.items = [];
this.set = new Set();
}
add(item) {
if (this.set.has(item[this.key])) return;
this.set.add(item[this.key]);
this.items.push(item);
}
addMany(items) {
items.forEach(item => this.add(item));
}
}
var mySet = new ObjectSet('name');
mySet.addMany(family);
console.log(mySet.items);
I have my example array:
var person = [{
firstName:"John",
lastName:"Doe",
age:46
},
{
firstName:"Alexander",
lastName:"Bru",
age:46
},
{
firstName:"Alex",
lastName:"Bruce",
age:26
}];
Simple person.length gives me the length of my array, but I need to merge values when the age is the same. So if two people have same age return 1 no 2. Sorry for my bad English, I can made a mistakes.
Use Array#forEach method with an object reference for age.
var person = [{
firstName: "John",
lastName: "Doe",
age: 46
}, {
firstName: "Alexander",
lastName: "Bru",
age: 46
}, {
firstName: "Alex",
lastName: "Bruce",
age: 26
}];
// object for storing reference to age
var obj = {},
res = 0;
// iterate and count
person.forEach(function(v) {
// check age already not defined
if (!obj[v.age]) {
// define the property
obj[v.age] = true;
// increment count
res++;
}
});
console.log(res);
you can use underscore or similar library that supports groupBy:
_.size(_.groupBy(person, "age"))
Filter the array down to only those elements for which a find on the array for the first element with the same age yields the element itself, then take the length of the result:
array.filter(o1 => o1 === array.find(o2 => o2.age === o1.age)).length
Another idea involves using a little function called uniqueCount, which counts the number of unique values in a (sorted) array:
function uniqueCount(a) {
return a.reduce((cnt, elt, i) => a[i] === a[i-1] ? cnt : cnt + 1), 0);
}
Now you can create an array of all the ages, and do a count of its unique elements on it:
uniqueCount(array.map(e => e.age).sort(numeric))
If you are allowed to, you could add all the ages to a set and take its size.
const people = [{
firstName: "John",
lastName: "Doe",
age: 46
}, {
firstName: "Alexander",
lastName: "Bru",
age: 46
}, {
firstName: "Alex",
lastName: "Bruce",
age: 26
}];
const ages = new Set(people.map(person => person.age));
console.log(ages.size)