Fill in missing properties in an array of objects - javascript

What is the best way to fill in missing properties in an array of objects, such as this example:
[
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
I need to add the missing properties with a null value, so that when I pass this array on to be rendered in something such as a HTML table or CSV file, everything lines up correctly. I was thinking of passing over the array twice, once to get all the possible properties, and a second time to add those missing properties with a null value to each object where it doesn't exist. Is there a better way to do this?
EDIT: I won't know what the keys are until I have the data, it's coming from an API and the keys are not always requested explicitly.
My final solution
Thanks all, it seems the two pass approach is indeed the best approach. After I started to write this using the examples provided, I realised that the order of the properties wasn't being maintained. This is how I achieved filling in the missing props, and maintaining the correct order. Any suggestions for potential improvements are welcome.
var fillMissingProps = function(arr) {
// build a list of keys in the correct order
var keys = [];
arr.forEach(function(obj) {
var lastIndex = -1;
Object.keys(obj).forEach(function(key, i) {
if (keys.includes(key)) {
// record the position of the existing key
lastIndex = keys.lastIndexOf(key);
if (lastIndex < i) {
// this key is in the wrong position so move it
keys.splice(i, 0, keys.splice(lastIndex, 1)[0]);
lastIndex = i;
}
} else {
// add the new key in the correct position
// after the previous existing key
lastIndex++;
keys.splice(lastIndex, 0, key);
}
});
});
// build a template object with all props set to null
// and in the correct position
var defaults = {};
keys.forEach(function(key) {
defaults[key] = null;
});
// and update the array by overwriting each element with a
// new object that's built from the template and the original object
arr.forEach(function(obj, i, arr) {
arr[i] = Object.assign({}, defaults, obj);
});
return arr;
};
/** TEST **/
var currentArray = [
{
website: 'http://www.unknown.com'
},
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
title: 'Mr',
name: 'Richard',
gender: 'Male',
number: '04321 666 555'
},
{
id: '003ABCDEFGHIJKL',
name: 'Harry',
website: 'http://www.harry.com',
mobile: '07890 123 456',
city: 'Brentwood',
county: 'Essex'
}
];
var newArray = fillMissingProps(currentArray);
for (var i = 0; i < newArray.length; i++) {
for (var prop in newArray[i]) {
console.log(prop + ": " + newArray[i][prop]);
}
console.log('---------');
}

Given that you don't know apriori which keys are supposed to exist, you have no choice but to iterate over the array twice:
// build a map of unique keys (with null values)
var keys = {}
array.forEach(el => Object.keys(el).forEach(k => keys[k] = null));
// and update the array by overwriting each element with a
// new object that's built from the null map and the original object
array.forEach((el, ix, a) => a[ix] = Object.assign({}, keys, el));

Use Array.prototype.map():
const arr = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com',
},
{
name: 'Richard',
number: '07777 666 555',
},
{
name: 'Harry',
website: 'http://www.harry.com',
},
];
const newArr = arr.map(x => (
arr.map(x => Object.keys(x))
.reduce((a, b) =>
(b.forEach(z => a.includes(z) || a.push(z)), a)
)
.forEach(
y => (x[y] = x.hasOwnProperty(y) ? x[y] : null)
), x)
);
console.log(newArr);

Here is a more interesting answer, its a tad fun one but it will build up your objects on the fly as new properties appear:
var currentArray = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number: '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
var newArray = []
function NewObject() {
}
for(var i = 0; i < currentArray.length; i++){
var nObj = new NewObject();
for(var prop in currentArray[i]){
if(!NewObject.hasOwnProperty(prop))
NewObject.prototype[prop] = null;
nObj[prop]=currentArray[i][prop];
}
newArray.push(nObj);
}
for(var i = 0; i < newArray.length; i++){
for(var prop in newArray[i]){
console.log(prop+ ": "+newArray[i][prop]);
}
console.log('---------');
}
It builds new objects from the ones you provide and adds new properties to the objects if they don't exist already.
This idea was more for curiosities sake tho so any comments would be interesting :)

You can get all keys and set all keys using for..of loop, .map() to iterate all Object.keys(), redefine original array
var arr = [{
name: 'Harry',
website: 'http://www.harry.com'
},{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
}, {
name: 'Richard',
number: '07777 666 555'
}];
for (var obj of arr) {
for (var key of Object.keys(obj)) {
arr = arr.map(o => (o[key] = o[key] || null, o))
}
};
console.log(arr);

Something like this could work:
for (var i = 0; i < arrayLength; i++) {
yourArray[i].name = yourArray[i].name || null;
yourArray[i].number = yourArray[i].number || null;
yourArray[i].website= yourArray[i].website|| null;
}

Related

How to invert the structure of nested array of objects in Javascript?

I currently have an array that has the following structure:
data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
But I would like to restructure the array to get something like this:
data = [
{
name: "thing1",
info: [{
time: 100,
count: 3
}, {
time: 1000,
count: 7
}, {
}]
},
{
name: "thing2",
info: [{
time: 100,
count: 2
}, {
time: 1000,
count: 0
}, {
}]
}
];
So basically the key would have to be switched from time to name, but the question is how. From other posts I have gathered that using the map function might work, but since other posts had examples to and from different structures I am still not sure how to use this.
There are a number of ways to achieve this however, the key idea will be to perform a nested looping of both data items and their (nested) info items. Doing that allows your algorithm to "visit" and "map" each piece of input data, to a corresponding value in the resulting array.
One way to express that would be to use nested calls to Array#reduce() to first obtaining a mapping of:
name -> {time,count}
That resulting mapping would then be passed to a call to Object.values() to transform the values of that mapping to the required array.
The inner workings of this mapping process are summarized in the documentation below:
const data=[{time:100,info:[{name:"thing1",count:3},{name:"thing2",count:2},{}]},{time:1e3,info:[{name:"thing1",count:7},{name:"thing2",count:0},{}]}];
const result =
/* Obtain array of values from outerMap reduce result */
Object.values(
/* Iterate array of data items by reduce to obtain mapping of
info.name to { time, count} value type */
data.reduce((outerMap, item) =>
/* Iterate inner info array of current item to compound
mapping of info.name to { time, count} value types */
item.info.reduce((innerMap, infoItem) => {
if(!infoItem.name) {
return innerMap
}
/* Fetch or insert new { name, info } value for result
array */
const nameInfo = innerMap[ infoItem.name ] || {
name : infoItem.name, info : []
};
/* Add { time, count } value to info array of current
{ name, info } item */
nameInfo.info.push({ count : infoItem.count, time : item.time })
/* Compound updated nameInfo into outer mapping */
return { ...innerMap, [ infoItem.name] : nameInfo }
}, outerMap),
{})
)
console.log(result)
Hope that helps!
The approach I would take would be to use an intermediate mapping object and then create the new array from that.
const data = [{time: 100, info: [{name: "thing1", count: 3}, {name: "thing2", count: 2}, {}]}, {time: 1e3, info: [{name: "thing1", count: 7}, {name: "thing2", count: 0}, {}]} ];
const infoByName = {};
// first loop through and add entries based on the name
// in the info list of each data entry. If any info entry
// is empty ignore it
data.forEach(entry => {
if (entry.info) {
entry.info.forEach(info => {
if (info.name !== undefined) {
if (!infoByName[info.name]) {
infoByName[info.name] = [];
}
infoByName[info.name].push({
time: entry.time,
count: info.count
});
}
});
}
});
// Now build the resulting list, where name is entry
// identifier
const keys = Object.keys(infoByName);
const newData = keys.map(key => {
return {
name: key,
info: infoByName[key]
};
})
// newData is the resulting list
console.log(newData);
Well, the other guy posted a much more elegant solution, but I ground this one out, so I figured may as well post it. :)
var data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
var newArr = [];
const objInArray = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o)
return true;
}
return false;
}
const getIndex = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o) {
return i;
}
}
return false;
}
const getInfoObj = (t, c) => {
let tmpObj = {};
tmpObj.count = c;
tmpObj.time = t;
return tmpObj;
}
for (var i=0; i < data.length; i += 1) {
let t = data[i].time;
for (var p in data[i].info) {
if ("name" in data[i].info[p]) {
if (objInArray(data[i].info[p].name, newArr)) {
let idx = getIndex(data[i].info[p].name, newArr);
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newArr[idx].info.push(newInfoObj);
} else {
let newObj = {};
newObj.name = data[i].info[p].name;
let newInfo = [];
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newInfo.push(newInfoObj);
newObj.info = newInfo;
newArr.push(newObj);
}}
}
}
console.log(newArr);
try to use Object.keys() to get the key

retriving values from javascript object and then convert it to one object

I have a problem! I am creating an rating app, and I have come across a problem that I don't know how to solve. The app is react native based so I am using JavaScript.
The problem is that I have multiple objects that are almost the same, I want to take out the average value from the values of the "same" objects and create a new one with the average value as the new value of the newly created object
This array in my code comes as a parameter to a function
var arr = [
{"name":"foo","value":2},
{"name":"foo","value":5},
{"name":"foo","value":2},
{"name":"bar","value":2},
{"name":"bar","value":1}
]
and the result I want is
var newArr = [
{"name":"foo","value":3},
{"name":"bar","value":1.5},
]
If anyone can help me I would appreciate that so much!
this is not my exact code of course so that others can take help from this as well, if you want my code to help me I can send it if that's needed
If you have any questions I'm more than happy to answer those
Iterate the array with Array.reduce(), and collect to object using the name values as the key. Sum the Value attribute of each name to total, and increment count.
Convert the object back to array using Object.values(). Iterate the new array with Array.map(), and get the average value by dividing the total by count:
const arr = [{"name":"foo","Value":2},{"name":"foo","Value":5},{"name":"foo","Value":2},{"name":"bar","Value":2},{"name":"bar","Value":1}];
const result = Object.values(arr.reduce((r, { name, Value }) => {
if(!r[name]) r[name] = { name, total: 0, count: 0 };
r[name].total += Value;
r[name].count += 1;
return r;
}, Object.create(null)))
.map(({ name, total, count }) => ({
name,
value: total / count
}));
console.log(result);
I guess you need something like this :
let arr = [
{name: "foo", Value: 2},
{name: "foo", Value: 5},
{name: "foo", Value: 2},
{name: "bar", Value: 2},
{name: "bar", Value: 1}
];
let tempArr = [];
arr.map((e, i) => {
tempArr[e.name] = tempArr[e.name] || [];
tempArr[e.name].push(e.Value);
});
var newArr = [];
$.each(Object.keys(tempArr), (i, e) => {
let sum = tempArr[e].reduce((pv, cv) => pv+cv, 0);
newArr.push({name: e, value: sum/tempArr[e].length});
});
console.log(newArr);
Good luck !
If you have the option of using underscore.js, the problem becomes simple:
group the objects in arr by name
for each group calculate the average of items by reducing to the sum of their values and dividing by group length
map each group to a single object containing the name and the average
var arr = [
obj = {
name: "foo",
Value: 2
},
obj = {
name: "foo",
Value: 5
},
obj = {
name: "foo",
Value: 2
},
obj = {
name: "bar",
Value: 2
},
obj = {
name: "bar",
Value: 1
}
]
// chain the sequence of operations
var result = _.chain(arr)
// group the array by name
.groupBy('name')
// process each group
.map(function(group, name) {
// calculate the average of items in the group
var avg = (group.length > 0) ? _.reduce(group, function(sum, item) { return sum + item.Value }, 0) / group.length : 0;
return {
name: name,
value: avg
}
})
.value();
console.log(result);
<script src="http://underscorejs.org/underscore-min.js"></script>
In arr you have the property Value and in newArr you have the property value, so I‘ll assume it to be value both. Please change if wished otherwise.
var map = {};
for(i = 0; i < arr.length; i++)
{
if(typeof map[arr[i].name] == ‘undefined‘)
{
map[arr[i].name] = {
name: arr[i].name,
value: arr[i].value,
count: 1,
};
} else {
map[arr[i].name].value += arr[i].value;
map[arr[i].name].count++;
}
var newArr = [];
for(prop in map)
{
map[prop].value /= map[prop].count;
newArr.push({
name: prop,
value: map[prop].value
});
}
delete map;

How to merge 2 array using javascript?

I am going to merge 2 array with one array key and other array with key and get only other array value and create final one array in the last. i have write some logic here but i am just put first array key push but other array value can't push in the final array. so anyone know how can do that then please let me here. here i have listed my code with array.
This is my first array=>
var arr = ["fullName","username","biography","externalurl","followerCount","followingCount","medaiCount"];
This is my other array =>
var FinalFilterArray = [ { fullName: 'love',
username: 'lo.ve632',
biography: '',
externalUrl: '',
followerCount: 13,
followingCount: 129,
mediaCount: 0 },
{ fullName: 'abc',
username: '#abc',
biography: '',
externalUrl: '',
followerCount: 289,
followingCount: 262,
mediaCount: 0 }];
This is my logic =>
var ExcelData = [];
for (var i = 0; i < FinalFilterArray.length; i++) {
console.log("f" + FinalFilterArray.length)
if (i == 0) {
ExcelData[i] = arr
}
else {
var key = [];
for (var j = 0; j < arr.length; j++) {
console.log("j " + arr[j]) if(FinalFilterArray[i] == arr[j]){key.push[FinalFilterArray[i].arr[j]]}
}
ExcelData[i] = [key]
}
}
my Expected o\p =>
[[ 'fullName',
'username',
'biography',
'externalUrl',
'followerCount',
'followingCount',
'mediaCount' ],
['love','lo.ve632','','','13','129','0'] ,
['abc','#abc','','','289','262','0']]
finalArr = [ arr , ...FinalFilterArray.map(item => arr.map(key => item[key])) ]
If you want an es5 solution to this, use
finalArr = [arr].concat(FinalFilterArray.map(function(item){
return arr.map(function(key) {
return item[key]
})
})

Merge objects concatenating values, using lodash

I'm trying to manipulate this sample array of objects.
[ { name: 'John Wilson',
id: 123,
classes: ['java', 'c++']},
{ name: 'John Wilson',
id: 123,
classes: 'uml'},
{ name: 'Jane Smith',
id: 321,
classes: 'c++'} ]
What I need to do is to merge objects with the same 'id', concatenating 'classes' and keeping one 'name'.
The result should be:
[ { name: 'John Wilson',
id: 123,
classes: ['java', 'c++', 'uml']},
{ name: 'Jane Smith',
id: 321,
classes: 'c++'} ]
I tried using .merge but it doesn't concatenate the values from 'classes', it just keeps the values from the last equal object.
What is the simplest way to do that, using lodash?
The function you're looking for is _.uniqWith, with a special twist which I will explain in a minute.
_.uniqWith is a lot like _.uniq in that it generates a unique array, but it allows you to pass your own custom comparator function that will be called to determine what counts as "equality."
Sane programmers would understand that this comparator should be side-effect free. The way this code works is by breaking that rule, and using a comparison function that does extra magic behind the scenes. However, this results in very concise code that will work no matter how many of these objects are in your array, so I feel like the transgression is well-justified.
I named the comparator function compareAndMerge so as not to hide its impure nature. It will merge both classes arrays and update the relevant property on both objects, but only if their id values are identical.
function merge(people) {
return _.uniqWith(people, compareAndMerge)
}
function compareAndMerge(first, second) {
if (first.id === second.id) {
first.classes = second.classes = [].concat(first.classes, second.classes)
return true
}
return false
}
var people = [{
name: 'John Wilson',
id: 123,
classes: ['java', 'c++']
}, {
name: 'John Wilson',
id: 123,
classes: 'uml'
}, {
name: 'Jane Smith',
id: 321,
classes: 'c++'
}]
console.log(merge(people))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
An aside: You were missing square brackets around your original classes lists. I made sure that the code above doesn't care whether or not the classes property holds a single string or an array of strings, though, just in case.
Using ES6 you can do so with a Map to hold the unique values, Array#reduce to populate it, and the spread operator with Map#values to convert it back to array:
const arr = [{"name":"John Wilson","id":123,"classes":["java","c++"]},{"name":"John Wilson","id":123,"classes":"uml"},{"name":"Jane Smith","id":321,"classes":"c++"}];
const result = [...arr.reduce((hash, { id, name, classes }) => {
const current = hash.get(id) || { id, name, classes: [] };
classes && (current.classes = current.classes.concat(classes));
return hash.set(id, current);
}, new Map).values()];
console.log(result);
Not sure using lodash... here's a way to do it with normal JS:
var combined = arr.reduce(function(a, item, idx) {
var found = false;
for (var i = 0; i < a.length; i++) {
if (a[i].id == item.id) {
a[i].classes = a[i].classes.concat(item.classes);
found = true;
break;
}
}
if (!found) {
a.push(item);
}
return a;
}, []);
Fiddle: https://jsfiddle.net/6zwr47mt/
use _.mergeWith to set merging customizer
_.reduce(data, function(result, item) {
item = _.mergeWith(
item,
_.find(result, {id: item.id}),
function(val, addVal) {
return _.isArray(val) ? _.concat(val, addVal) : val;
});
result = _.reject(result, {id: item.id})
return _.concat(result, item);
}, []);
The following algorithm is not the best one but at least I know what it does :-)
console.log(clean(data));
function clean (data) {
var i, x, y;
var clean = [];
var m = clean.length;
var n = data.length;
data.sort((x, y) => x.id - y.id);
for (i = 0; i < n; i++) {
y = data[i];
if (i == 0 || x.id != y.id) {
clean.push(x = clone(y)), m++;
} else {
clean[m - 1] = merge(x, y);
}
}
return clean;
}
function clone (x) {
var z = {};
z.id = x.id;
z.name = x.name;
z.classes = x.classes.slice();
return z;
}
function merge (x, y) {
var z = {};
z.id = x.id;
z.name = x.name;
z.classes = unique(
x.classes.concat(y.classes)
);
return z;
}
function unique (xs) {
var i, j, n;
n = xs.length;
for (i = 1; i < n; i++) {
j = 0; while (j < i && xs[i] !== xs[j]) j++;
if (j < i) swap(xs, i, n - 1), i--, n--;
}
return xs.slice(0, n);
}
function swap (xs, i, j) {
var x = xs[i];
xs[i] = xs[j];
xs[j] = x;
}
<script>
var data = [{
id: 123,
name: 'John Wilson',
classes: ['java', 'c++']
}, {
id: 123,
name: 'John Wilson',
classes: ['uml', 'java']
}, {
id: 321,
name: 'Jane Smith',
classes: ['c++']
}];
</script>

How can i find an Object key with the longest char length in an array of objects

Say i have a collection of objects like so
var collection = [
{
name:"John",
age:12,
location:"Califonia",
gender:"Male"
},
{
name:"Jane",
age:18,
location:"New york",
gender:"Female"
}
]
it is obvious "location" is the object key with the longest character length.
But how can i get this dynamically as i dont know how collection will be structured in advance.
With these functions:
/**
* Get the longest key in an object.
*/
function longestKey(obj) {
return Object.keys(obj).reduce(function(all, current) {
return all.length > current.length ? all : current;
});
}
/**
* Get the object with the longest key from array of objects.
*/
function longestKeyObj(arr) {
return arr.reduce(function(all, current) {
return longestKey(all).length > longestKey(current).length ? all : current;
});
}
You can do:
const me = {
name: 'nabil',
occupation: 'programming',
dob: '1984-12-28',
};
const you = {
longestKey: 'blah!',
};
console.log('The object is: ', longestKeyObj([me, you]));
console.log('The key is: ', longestKey(longestKeyObj([me, you])));
Which outputs:
The object is: { longestKey: 'blah!' }
The key is: longestKey
Demo: https://repl.it/#kadimi/longestKey
if you are sure the items in the collection will always have the same structure you can simple do this.
var collection = [
{
name:"John",
age:12,
location:"Califonia",
gender:"Male"
},
{
name:"Jane",
age:18,
location:"New york",
gender:"Female"
}
]
function getMaxKeys(collection){
var keys = Object.keys(collection[0]);
var keyWithMaxWidth = keys.reduce(function(prev, current){
if(prev.length > current.length) return prev;
else return current
});
return keyWithMaxWidth;
}
getMaxKeys(collection) //location
you can get array object key of specific object by command :
Object.keys(<object>)
Make a loop for find longest key. In your problem you need loop for all object in array
The code below will solve your problem in optimal way:
var sorted = (Object.keys(collection[0])).sort(function(a, b){
return b.length - a.length;
});
sorted[0]; // Location
You can loop each object in the array and then loop each property in the object like so:
var collection = [
{
name:"John",
age:12,
location:"Califonia",
gender:"Male"
},
{
name:"Jane",
age:18,
location:"New york",
gender:"Female"
},
{
veryLong: "This key will definitely be the longest"
}
];
var stats = {
key: '',
valLen: 0
};
collection.forEach(function(obj){
for(var prop in obj){
if(obj[prop].length > stats.valLen){
stats.key = prop
stats.valLen = obj[prop].length
}
}
});
console.log(stats.key);
console.log(stats.valLen);
Each item in the array may have a different format.
This example will log: 'veryLong' as longest key and 39 as its length.
See this fiddle
Just loop through the keys with Object.keys(obj) and pick up the longest one, Use the following function:
var getLongestKey = function getLongestKey(obj) {
for (key in keys) {
if (keys[key].length > longest.length) {
longest = keys[key];
}
}
}
And this is a working demo, to get the longest key for all the objects in the array :
var collection = [{
name: "John",
age: 12,
location: "Califonia",
gender: "Male"
}, {
name: "Jane",
age: 18,
location: "New york",
gender: "Female"
}];
var getLongestKey = function getLongestKey(obj) {
for (key in keys) {
if (keys[key].length > longest.length) {
longest = keys[key];
}
}
}
for (i = 0; i < collection.length; i++) {
var obj = collection[i];
var keys = Object.keys(obj);
var longest = keys[0];
getLongestKey(obj);
alert("Longest key in Object " + i + " is : " + longest);
}

Categories