0

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

8
  • Or you could just pass over it once, when you render it and pass in null (or whatever) for the keys you don't have. Commented Nov 3, 2016 at 20:45
  • 1
    do you not know apriori which keys are supposed to exist ? Commented Nov 3, 2016 at 20:48
  • Do you have a genuine need to support arbitrary properties? If not, use Object.assign with a default object that has all the keys. If you do, then you'll have to get all the keys, create a default object, then you could do the same thing. Commented Nov 3, 2016 at 20:50
  • I won't know what the keys are until I get the data. Commented Nov 3, 2016 at 20:51
  • @marktuk it seems you have no choice but to iterator over the array twice, then. Commented Nov 3, 2016 at 20:55

5 Answers 5

5

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));
Sign up to request clarification or add additional context in comments.

3 Comments

@DaveNewton unfortunately it seems you can't use Object.assign(orig, keys, orig) to update the properties in-place.
Right; duh, my bad.
@Alnitak this is definitely the best approach. I've updated above with my final solution based on this method.
0

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

2 Comments

So this is what I had in mind, but I would still need to do an initial pass over the array to get all the possible keys. I won't know what they are until I've done this unfortunately, and the array could be quite large.
OP does not know what keys will be.
0

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

5 Comments

This is quite interesting, you've done it in a single pass of the array, very clever. I think this is my favourite solution, very elegant. I wonder if there are any caveats to this approach?
So one drawback I've found is the order of the properties is not maintained. You can see this if you run your example.
whilst cute, this method will break if you subsequently use any enumeration method that only includes an object's "own properties" and doesn't include those inherited from the prototype chain.
@Alnitak Yea I see, its all good, the idea behind this code was for curiosity and cuteness sake :)
@marktuk Yea I wouldn't use this I just wanted to see what was possible, and peoples opinions on it :) If you play with it more you may get it in a nicer state but I would definitely go with Alnitak's answer for stability's sake :)
0

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

8 Comments

wuh?! The one with the largest number of keys might have keys that are completely disjoint from the others.
@Alnitak What do you mean by "disjoint"?
The object with the greatest number of keys may not have all the properties that other objects with less number of keys do.
@Terry Still not sure what you mean? Can you share an example? The object at OP does not have the key present, or has the key present. Do you mean an array other than what OP expects and describes at Question?
One object may contain key1, key2, key3...keyN and another object may contain key(N+1). In this case the first object, even though having the greatest number of keys, does not contain any keys from the latter.
|
-1

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

1 Comment

Unfortunately I won't know what the keys will be until I have the data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.