marktuk marktuk - 1 month ago 7
JSON Question

Fill in missing properties in an array of objects

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('---------');
}

Answer

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));