Related
This 2 arrays have multiple objects that has the the same ID but different dates
const names= [
{id:'1',name:'a',date:'1604616214'},
{id:'1',name:'Angel',date:'1604616215'},
{id:'2',name:'b',date:'2004616214'},
{id:'2',name:'Karen',date:'2004616215'},
{id:'3',name:'a',date:'3004616220'},
{id:'3',name:'Erik',date:'3004616221'}
]
const lastnames= [
{id:'1',lastname:'a',date:'4004616220'},
{id:'1',lastname:'Ferguson',date:'4004616221'},
{id:'2',lastname:'b',date:'5004616220'},
{id:'2',lastname:'Nixon',date:'5004616221'},
{id:'3',lastname:'a',date:'6004616222'},
{id:'3',lastname:'Richard',date:'6004616223'}
]
The data is in moment().unix() to create a number "easy to compare"
I want to create a Third array that merge the 2 arrays and create objects with the same id and the last updated date object.
The output should be something like this
const third = [
{id:'1',name:'Angel',lastname:'Ferguson'},
{id:'2',name:'Karen',lastname:'Nixon'},
{id:'3',name:'Erik',lastname:'Richard'}
]
This is what i got so far, if i updated the arrays it duplicates and i need to have only the last updated object
const third = names.map(t1 => ({...t1, ...lastnames.find(t2 => t2.id === t1.id)}))
I'm going to assume since you have the spread operator and Array.find in your example that you can use ES6, which includes for of and Object.values as you see below.
An object and simple looping is used to reduce the amount of times you're iterating. In your example, for every element in names you're iterating over last names to find one with the same ID. Not only is that not ideal for performance, but it doesn't work because every time you're finding the same element with that ID (the first one with that ID in the array).
const names = [
{ id: "1", name: "a", date: "1604616214" },
{ id: "1", name: "Angel", date: "1604616215" },
{ id: "2", name: "b", date: "2004616214" },
{ id: "2", name: "Karen", date: "2004616215" },
{ id: "3", name: "a", date: "3004616220" },
{ id: "3", name: "Erik", date: "3004616221" },
];
const lastnames = [
{ id: "1", lastname: "a", date: "4004616220" },
{ id: "1", lastname: "Ferguson", date: "4004616221" },
{ id: "2", lastname: "b", date: "5004616220" },
{ id: "2", lastname: "Nixon", date: "5004616221" },
{ id: "3", lastname: "a", date: "6004616222" },
{ id: "3", lastname: "Richard", date: "6004616223" },
];
const profiles = {};
function addToProfiles(arr, profiles) {
for (let obj of arr) {
if (obj.id != null) {
// Inits to an empty object if it's not in the profiles objects
const profile = profiles[obj.id] || {};
profiles[obj.id] = { ...profile, ...obj };
}
}
}
addToProfiles(names, profiles);
addToProfiles(lastnames, profiles);
const third = Object.values(profiles);
The idea is to group the objects by their ids, then merge each group according to the rules, maximizing date for each type of record (name and lastname)
// the input data
const names= [
{id:'1',name:'a',date:'1604616214'},
{id:'1',name:'Angel',date:'1604616215'},
{id:'2',name:'b',date:'2004616214'},
{id:'2',name:'Karen',date:'2004616215'},
{id:'3',name:'a',date:'3004616220'},
{id:'3',name:'Erik',date:'3004616221'}
]
const lastnames= [
{id:'1',lastname:'a',date:'4004616220'},
{id:'1',lastname:'Ferguson',date:'4004616221'},
{id:'2',lastname:'b',date:'5004616220'},
{id:'2',lastname:'Nixon',date:'5004616221'},
{id:'3',lastname:'a',date:'6004616222'},
{id:'3',lastname:'Richard',date:'6004616223'}
]
// make one long array
let allNames = [...names, ...lastnames]
// a simple version of lodash _.groupBy, return an object like this:
// { '1': [ { objects with id==1 }, '2': [ ... and so on ] }
function groupById(array) {
return array.reduce((acc, obj) => {
let id = obj.id
acc[id] = acc[id] || [];
acc[id].push(obj);
return acc;
}, {});
}
// this takes an array of objects and merges according to the OP rule
// pick the maximum date name object and maximum date lastname object
// this sorts and searches twice, which is fine for small groups
function mergeGroup(id, group) {
let sorted = group.slice().sort((a, b) => +a.date < +b.date)
let name = sorted.find(a => a.name).name
let lastname = sorted.find(a => a.lastname).lastname
return {
id,
name,
lastname
}
}
// first group, then merge
let grouped = groupById(allNames)
let ids = Object.keys(grouped)
let results = ids.map(id => {
return mergeGroup(id, grouped[id])
})
console.log(results)
I tried to come up with a solution using filter functions. End result contains the format you wanted. check it out.
const names= [
{id:'1',name:'a',date:'1604616214'},
{id:'1',name:'Angel',date:'1604616215'},
{id:'2',name:'b',date:'2004616214'},
{id:'2',name:'Karen',date:'2004616215'},
{id:'3',name:'a',date:'3004616220'},
{id:'3',name:'Erik',date:'3004616221'}
]
const lastnames= [
{id:'1',lastname:'a',date:'4004616220'},
{id:'1',lastname:'Ferguson',date:'4004616221'},
{id:'2',lastname:'b',date:'5004616220'},
{id:'2',lastname:'Nixon',date:'5004616221'},
{id:'3',lastname:'a',date:'6004616222'},
{id:'3',lastname:'Richard',date:'6004616223'}
]
// filter out last updated objects from both arrays
var lastUpdatednames = names.filter(filterLastUpdate,names);
console.log(lastUpdatednames);
var lastUpdatedsurnames = lastnames.filter(filterLastUpdate,lastnames);
console.log(lastUpdatedsurnames);
// combine the properties of objects from both arrays within filter function.
const third = lastUpdatednames.filter(Combine,lastUpdatedsurnames);
console.log(third);
function filterLastUpdate(arrayElement)
{
var max = this.filter( i => arrayElement.id==i.id ).reduce(
function(prev, current)
{
return (prev.date > current.date) ? prev : current
}
)
return max.date == arrayElement.date ;
}
function Combine(firstArray)
{
var subList= this.filter( i => firstArray.id==i.id );
//console.log(subList);
//console.log(subList[0]);
if (subList)
{
firstArray.lastname = subList[0].lastname;
return true;
}
return false ;
}
Here is last output:
[…]
0: {…}
date: "1604616215"
id: "1"
lastname: "Ferguson"
name: "Angel"
1: {…}
date: "2004616215"
id: "2"
lastname: "Nixon"
name: "Karen"
2: {…}
date: "3004616221"
id: "3"
lastname: "Richard"
name: "Erik"
The desired output should be as follows. I tried object restructuring way but i could not push the out as an object. If you can just guide me what are the other array methods i can use to get the desired array
const sample = [
{
name: 'Bike',
series: [
{ date: '01-01-2020', value: '4$' },
{ date: '02-01-2020', value: '3$' },
{ date: '03-01-2020', value: '3.5$' }
]
},
{
name: 'Watch',
series: [
{ date: '01-01-2020', value: '1$' },
{ date: '02-01-2020', value: '2$' },
{ date: '03-01-2020', value: '5$' }
]
}
]
const output = [
{ date: '01-01-2020', 'bike-value': '4$', 'watch-value': '1$' },
{ date: '02-01-2020', 'bike-value': '3$', 'watch-value': '2$' },
{ date: '03-01-2020', 'bike-value': '3.5$', 'watch-value': '5$'}
]
What i tried is as follows. But i cannot make this into a object to push into an empty array.
for (const {name: n, series: [{date: d , value: v}]} of sample) {
console.log('name: ' + n + ', date: ' + d + ', value: ' + v);
}
You could loop through the sample array and then loop through the each series array. Create a group object which has each date as key and the object needed in the final output it's value. Use Object.values() to get the values of the group object as an array
const sample=[{name:"Bike",series:[{date:"01-01-2020",value:"4$"},{date:"02-01-2020",value:"3$"},{date:"03-01-2020",value:"3.5$"}]},{name:"Watch",series:[{date:"01-01-2020",value:"1$"},{date:"02-01-2020",value:"2$"},{date:"03-01-2020",value:"5$"}]}];
const group = {}
for (const { name, series } of sample) {
for (const { date, value } of series) {
group[date] = group[date] || { date };
group[date][`${name.toLowerCase()}-value`] = value
}
}
const output = Object.values(group)
console.log(output)
The group object looks like this:
{
"01-01-2020": {
"date": "01-01-2020",
"bike-value": "4$",
"watch-value": "1$"
},
"02-01-2020": {
"date": "02-01-2020",
"bike-value": "3$",
...
},
"03-01-2020": {
....
}
A simple nested constructor should work here:
const sample = [
{name : 'Bike', series :
[{date : '01-01-2020', value : '4$'},
{date : '02-01-2020', value : '3$'},
{date : '03-01-2020', value : '3.5$'}]
},
{name : 'Watch', series :
[{date : '01-01-2020', value : '1$'},
{date : '02-01-2020', value : '2$'},
{date : '03-01-2020', value : '5$'}]
}
];
let results = [];
for (let i = 0; i< sample[0].series.length; i++){
//get date and 'Bike' value from first value
let newEntry = {date : sample[0].series[i].date, bikeValue : sample[0].series[i].value};
//find the corresponding 'Watch' value with another loop
let watchValue = 0;
for (let i2 = 0; i2<sample[1].series.length; i2++){
if(sample[1].series[i2].date == newEntry.date){
watchValue = sample[1].series[i2].value;
}
}
newEntry.watchValue = watchValue;
//push new object into results array
results.push(newEntry);
}
console.log(results);
I have the object coming in from server as shown below :-
[
{
"gender": {
"male": 25,
"female": 22
},
"population": {
"pop_under": -23 ,
"pop_over": 10,
}
}
]
I want to extract the key as nameand value as value for above object which after modification looks like this :-
[
{
gender : [
{
name : "male",
value : 25
},
{
name : "female",
value : 22
}
],
population : [
{
name: "pop_under" ,
value : "-23"
},
{
name: "pop_over" ,
value : "10"
}
]
}
]
How can perform said modification? And is it useful to use lodashpackage for this ?
Thank you very much for your help.
You can user reducer for creating new object and Object.keys for looping object fields:
const newData = data.reduce((acc,rec) => {
return [...acc, Object.keys(rec).reduce((iacc,irec)=>{
return {...iacc,
[irec]: (Object.keys(rec[irec]).map((item)=>{
return {name: item, value: rec[irec][item]}
}))
}
},{})]
}, [])
See full example in the playground: https://jscomplete.com/playground/s528735
Using .map and Object.keys method.
let result =[];
data.forEach(obj => {
Object.keys(obj).forEach(key=>{
let response = Object.
keys(obj[key]).
map(property=> (
{
name: `${property}`,
value:obj[key][property]
}
)
);
result.push({[key]:response});
});
});
console.log(result);
Sorry for the title, It's limited to 150 characters.
Full code example:
https://jsfiddle.net/c81zw30m/
Data:
Let's say I make an API request and I get this JSON object returned:
[
{
id: 123,
person: {
data: {
name: 'John',
language: 'Javascript'
}
},
details: {
age: 25
},
has_experience: true
},
{
id: 456,
person: {
data: {
name: 'Peter',
language: null // here we have null as a value.
}
},
details: {
age: 40
},
has_experience: false
},
{
id: 789,
person: {
data: {
name: 'Paul',
language: 'Python'
}
},
details: {
age: 30
},
has_experience: null // and here we also don't know if the person is available
},
];
Goal:
The end goal here is to iterate over the array and end up with new array of objects with different key names. Say for example I want to replace the key of person with human or the key of available with availability.
Additionally (optionally) we want to skip adding keys which value is equal to null.
Current solution:
let results = [];
for (let i=0; i< json.length; i++) {
results.push({
user_id: json[i].id,
name: json[i].person.data.name,
age: json[i].details.age,
has_experience: json[i].available ? json[i].available : false // here we are assigning a value no matter what using a ternary operator, what if we want no key:value pair here, just skip that pair
});
if (json[i].person.data.language) { results[i].language = json[i].person.data.language }
}
console.log(results);
Problem:
Now the example and solution I provided works, but imagine if the original API request had hundreds of key:value pairs, and many of them might be of null value.
Question:
Using modern javascript, is there any less verbose and more clean looking/elegant way to handle this problem?
Overall I am looking to create a brand new array of objects based on the original one, but with new key names where necessary. Additionally, we want to skip adding some of them if the value of the key is null for example.
Cheers.
EDIT:
Changed the key name from the example originally provided from available to has_experience because it was a bit misleading. I am not looking to filter out the original array of objects based on the value of a given key. If I wanted to do that I'd start with filter and then chain on.
What I want to do is to omit adding a key:value pair in the newly formed array if the value of the key is null for example.
Using lodash (or similar), you could get your mapping definition out of the mapping loop.
I find the following reasonably concise, though it can probably be shortened a little further.
import { get, set } from "lodash";
let json = [ ... ];
let mapping = new Map([
["user_id", "id"],
["name", "person.data.name"],
["age", "details.age"],
["availability", "available"],
["language", "person.data.language"],
["some.nested.property", "person.data.language"]
]);
var results = json.map(element => {
var mappedElement = {};
mapping.forEach((path, field, map) => {
var value = get(element, path);
if (value) {
set(mappedElement, field, value);
}
});
return mappedElement;
});
console.log(results);
Running this on your data yields
[Object, Object, Object]
0: Object
user_id: 123
name: "John"
age: 25
availability: true
language: "Javascript"
some: Object
nested: Object
property: "Javascript"
1: Object
user_id: 456
name: "Peter"
age: 40
2: Object
user_id: 789
name: "Paul"
age: 30
language: "Python"
some: Object
nested: Object
property: "Python"
Working example: https://codesandbox.io/s/mnkp79668
You can try something like this
You can achieve with map()
let json = [{id: 123, person: { data: { name: 'John', language: 'Javascript' } }, details: { age: 25 }, has_experience: true },
{id: 456, person: { data: { name: 'Peter',language: null } }, details: { age: 40 }, has_experience: false},
{id: 789, person: { data: { name: 'Paul', language: 'Python' } }, details: { age: 30 }, has_experience: null },];
let results = [];
results = json.map(current => {
let temp = {
user_id: current.id,
name: current.person.data.name,
age: current.details.age,
}
if (current.has_experience) {
temp.availablity = current.has_experience
}
if (current.person.data.language)
{ temp.language = current.person.data.language }
return temp;
})
console.log(results);
You have two separate problems to resolve. The first appears to be a requirement for generic flattening of the nested data structures within the input with out specifying every possible key that might exist.
This function will recursively flatten a nested object, along the way omitting any null values. However, this function might overwrite any values where the same key exists at multiple levels, so see below.
function flatten(obj, dest) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
flatten(obj[key], dest);
} else if (obj[key] !== null) {
dest[key] = obj[key];
}
}
return dest;
}
You also want to re-map some of the keys in your data, where the below function can be used both as a pre-processor to convert known duplicate keys into unique keys, and can also be used as a post-processor to convert particular keys back into nested objects. NB: requires "lodash".
function remap(obj, keys) {
for (let [in_key, out_key] of keys) {
let val = _.get(obj, in_key, null);
if (val !== null) {
_.unset(obj, in_key);
_.set(obj, out_key, val);
}
}
return obj;
}
The functions can be chained together like this:
let in_map = new Map([
['user.id', 'user_id']
]);
let out_map = new Map([
['available', 'test.availability']
]);
let out = data.map(obj => remap(obj, in_map))
.map(obj => flatten(obj, {}))
.map(obj => remap(obj, out_map));
I think what you want is to first filter the list, then map over the filtered results to create the new structure. This may not be especially performant however if the list is quite large.
const list = [
{
id: 123,
person: {
data: {
name: 'John',
language: 'Javascript'
}
},
details: {
age: 25
},
available: true
},
{
id: 456,
person: {
data: {
name: 'Peter',
language: null // here we have null as a value.
}
},
details: {
age: 40
},
available: false
},
{
id: 789,
person: {
data: {
name: 'Paul',
language: 'Python'
}
},
details: {
age: 30
},
available: null // and here we also don't know if the person is available
},
];
const newList = list.filter(listItem => listItem.available).map(filteredItem => {
return {
user_id: filteredItem.id,
name: filteredItem.person.data.name,
age: filteredItem.details.age,
availability: !!filteredItem.available
}
})
document.getElementById('list').innerText = JSON.stringify(list, null, 2);
document.getElementById('newList').innerText = JSON.stringify(newList, null, 2);
.container {
display: flex;
}
.container pre {
flex: 0 0 50%;
}
<div class="container">
<pre id="list"></pre>
<pre id="newList"></pre>
</div>
I have an array of objects:
Object = {
1 : { name : bob , dinner : pizza },
2 : { name : john , dinner : sushi },
3 : { name : larry, dinner : hummus }
}
I want to be able to search the object/array for where the key is "dinner", and see if it matches "sushi".
I know jQuery has $.inArray, but it doesn't seem to work on arrays of objects. Or maybe I'm wrong. indexOf also seems to only work on one array level.
Is there no function or existing code for this?
If you have an array such as
var people = [
{ "name": "bob", "dinner": "pizza" },
{ "name": "john", "dinner": "sushi" },
{ "name": "larry", "dinner": "hummus" }
];
You can use the filter method of an Array object:
people.filter(function (person) { return person.dinner == "sushi" });
// => [{ "name": "john", "dinner": "sushi" }]
In newer JavaScript implementations you can use a function expression:
people.filter(p => p.dinner == "sushi")
// => [{ "name": "john", "dinner": "sushi" }]
You can search for people who have "dinner": "sushi" using a map
people.map(function (person) {
if (person.dinner == "sushi") {
return person
} else {
return null
}
}); // => [null, { "name": "john", "dinner": "sushi" }, null]
or a reduce
people.reduce(function (sushiPeople, person) {
if (person.dinner == "sushi") {
return sushiPeople.concat(person);
} else {
return sushiPeople
}
}, []); // => [{ "name": "john", "dinner": "sushi" }]
I'm sure you are able to generalize this to arbitrary keys and values!
jQuery has a built-in method jQuery.grep that works similarly to the ES5 filter function from #adamse's Answer and should work fine on older browsers.
Using adamse's example:
var peoples = [
{ "name": "bob", "dinner": "pizza" },
{ "name": "john", "dinner": "sushi" },
{ "name": "larry", "dinner": "hummus" }
];
you can do the following
jQuery.grep(peoples, function (person) { return person.dinner == "sushi" });
// => [{ "name": "john", "dinner": "sushi" }]
var getKeyByDinner = function(obj, dinner) {
var returnKey = -1;
$.each(obj, function(key, info) {
if (info.dinner == dinner) {
returnKey = key;
return false;
};
});
return returnKey;
}
jsFiddle.
So long as -1 isn't ever a valid key.
If you're going to be doing this search frequently, consider changing the format of your object so dinner actually is a key. This is kind of like assigning a primary clustered key in a database table. So, for example:
Obj = { 'pizza' : { 'name' : 'bob' }, 'sushi' : { 'name' : 'john' } }
You can now easily access it like this: Object['sushi']['name']
Or if the object really is this simple (just 'name' in the object), you could just change it to:
Obj = { 'pizza' : 'bob', 'sushi' : 'john' }
And then access it like: Object['sushi'].
It's obviously not always possible or to your advantage to restructure your data object like this, but the point is, sometimes the best answer is to consider whether your data object is structured the best way. Creating a key like this can be faster and create cleaner code.
You can find the object in array with Alasql library:
var data = [ { name : "bob" , dinner : "pizza" }, { name : "john" , dinner : "sushi" },
{ name : "larry", dinner : "hummus" } ];
var res = alasql('SELECT * FROM ? WHERE dinner="sushi"',[data]);
Try this example in jsFiddle.
You can use a simple for in loop:
for (prop in Obj){
if (Obj[prop]['dinner'] === 'sushi'){
// Do stuff with found object. E.g. put it into an array:
arrFoo.push(Obj[prop]);
}
}
The following fiddle example puts all objects that contain dinner:sushi into an array:
https://jsfiddle.net/3asvkLn6/1/
There's already a lot of good answers here so why not one more, use a library like lodash or underscore :)
obj = {
1 : { name : 'bob' , dinner : 'pizza' },
2 : { name : 'john' , dinner : 'sushi' },
3 : { name : 'larry', dinner : 'hummus' }
}
_.where(obj, {dinner: 'pizza'})
>> [{"name":"bob","dinner":"pizza"}]
I had to search a nested sitemap structure for the first leaf item that machtes a given path. I came up with the following code just using .map() .filter() and .reduce. Returns the last item found that matches the path /c.
var sitemap = {
nodes: [
{
items: [{ path: "/a" }, { path: "/b" }]
},
{
items: [{ path: "/c" }, { path: "/d" }]
},
{
items: [{ path: "/c" }, { path: "/d" }]
}
]
};
const item = sitemap.nodes
.map(n => n.items.filter(i => i.path === "/c"))
.reduce((last, now) => last.concat(now))
.reduce((last, now) => now);
If You want to find a specific object via search function just try something like this:
function findArray(value){
let countLayer = dataLayer.length;
for(var x = 0 ; x < countLayer ; x++){
if(dataLayer[x].user){
let newArr = dataLayer[x].user;
let data = newArr[value];
return data;
}
}
return null;
}
findArray("id");
This is an example object:
layerObj = {
0: { gtm.start :1232542, event: "gtm.js"},
1: { event: "gtm.dom", gtm.uniqueEventId: 52},
2: { visitor id: "abcdef2345"},
3: { user: { id: "29857239", verified: "Null", user_profile: "Personal", billing_subscription: "True", partners_user: "adobe"}
}
Code will iterate and find the "user" array and will search for the object You seek inside.
My problem was when the array index changed every window refresh and it was either in 3rd or second array, but it does not matter.
Worked like a charm for Me!
In Your example it is a bit shorter:
function findArray(value){
let countLayer = Object.length;
for(var x = 0 ; x < countLayer ; x++){
if(Object[x].dinner === value){
return Object[x];
}
}
return null;
}
findArray('sushi');
We use object-scan for most of our data processing. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you would solve your question
// const objectScan = require('object-scan');
const findDinner = (dinner, data) => objectScan(['*'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.dinner === dinner
})(data);
const data = { 1: { name: 'bob', dinner: 'pizza' }, 2: { name: 'john', dinner: 'sushi' }, 3: { name: 'larry', dinner: 'hummus' } };
console.log(findDinner('sushi', data));
// => { name: 'john', dinner: 'sushi' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan