Damien
Damien

Reputation: 1600

How to Insert dates if some of the dates are missing in the original array of objects

Below is my data, which is an array of objects.

const myArray = [
{name: "Dave", date: "2020-07-30T17:30:46.180Z", score: 50, age: 20},
{name: "Dave", date: "2020-06-30T17:30:26.160Z", score: 35, age: 20},
{name: "Cosmo", date: "2020-06-30T17:30:26.160Z", score: 65, age: 22},
{name: "Dave", date: "2020-05-30T15:30:16.160Z", score: 65, age: 20},
{name: "Carlos", date: "2020-07-30T17:30:46.180Z", score: 70, age: 28},
{name: "Carlos", date: "2020-06-30T17:30:26.160Z", score: 53, age: 28},
];

The expected result is to make sure each individual has a score for each date. The score should be null when the date is missing for the individual.

Expected result

[
{name: "Dave", date: "2020-07-30T17:30:46.180Z", score: 50, age: 20},
{name: "Dave", date: "2020-06-30T17:30:26.160Z", score: 35, age: 20},
{name: "Dave", date: "2020-05-30T15:30:16.160Z", score: 65, age: 20},
{name: "Cosmo", date: "2020-06-30T17:30:26.160Z", score: 65, age: 22},
{name: "Cosmo", date: "2020-07-30T17:30:46.180Z", score: null, age: 22},
{name: "Cosmo", date: "2020-05-30T15:30:16.160Z", score: null, age: 22},
{name: "Carlos", date: "2020-07-30T17:30:46.180Z", score: 70, age: 28},
{name: "Carlos", date: "2020-06-30T17:30:26.160Z", score: 53, age: 28},
{name: "Carlos", date: "2020-05-30T15:30:16.160Z", score: null, age: 28}
];

My attempt to solve this problem can be found below. It didn't really work. What changes should I make?

const myArray = [
{name: "Dave", date: "2020-07-30T17:30:46.180Z", score: 50, age: 20},
{name: "Dave", date: "2020-06-30T17:30:26.160Z", score: 35, age: 20},
{name: "Cosmo", date: "2020-06-30T17:30:26.160Z", score: 65, age: 22},
{name: "Dave", date: "2020-05-30T15:30:16.160Z", score: 65, age: 20},
{name: "Carlos", date: "2020-07-30T17:30:46.180Z", score: 70, age: 28},
{name: "Carlos", date: "2020-06-30T17:30:26.160Z", score: 53, age: 28},
];
  
// Get unique dates
const getUniqueDates = (data) => {
    let temp = [];
    data.forEach(el => {

        if (temp.indexOf(el.date) === -1) {
            temp.push(el.date);
        }
    })
    return temp;
}

const myDates = getUniqueDates(myArray);

// Use a nested loop to verify whether date exists or not and insert objects
const getCleanData = () => {
    let temp = [];
    myDates.forEach(el => {

        for (let i = 0; i < myArray.length; i++) {
            let obj = {};
            obj['name'] = myArray[i].name;
            obj['age'] = myArray[i].age;
            if (el === myArray[i].date && typeof myArray[i].date !== 'undefined') {
                obj['score'] = myArray[i].score;
                obj['date'] = myArray[i].date;
                temp.push(obj);
            } else {
                obj['score'] = null;
                obj['date'] = el;
            }
        }

    })
    return temp;
}

console.log(getCleanData());

Upvotes: 0

Views: 70

Answers (4)

uke
uke

Reputation: 891

You need to know all possible dates first and the group by name, this will sort your data by name.

const allDates = Array.from(new Set(myArray.map(it=>it.date)))

Then use it in combination with groupBy

Object.entries(groupBy(myArray, (it) => it.name))
    .map(([name,personDates]) =>
        allDates.map(
            (date) =>
                personDates.find((it) => it.date === date) || {
                    name,
                    date,
                    score: null,
                    age: null,
                }
        )
    )
    .flat();

You can use _.groupBY or use this answer to recreate it Most efficient method to groupby on an array of objects

Finally you need to use flat to remove the array of arrays

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386540

You need a cartesian product of names and dates and store the names for getting a tamplate.

const
    data = [{ name: "Dave", date: "2020-07-30T17:30:46.180Z", score: 50, age: 20 }, { name: "Dave", date: "2020-06-30T17:30:26.160Z", score: 35, age: 20 }, { name: "Cosmo", date: "2020-06-30T17:30:26.160Z", score: 65, age: 22 }, { name: "Dave", date: "2020-05-30T15:30:16.160Z", score: 65, age: 20 }, { name: "Carlos", date: "2020-07-30T17:30:46.180Z", score: 70, age: 28 }, { name: "Carlos", date: "2020-06-30T17:30:26.160Z", score: 53, age: 28 }],
    temp = data.reduce((r, o) => {
        r.date[o.date] = true;
        r.name[o.name] = o;
        (r[o.name] ??= {})[o.date] = o;
        return r;
    }, { date: {}, name: {} }),
    result = Object
        .entries(temp.name)
        .flatMap(([name, o]) => Object
            .keys(temp.date)
            .map(date => temp[name]?.[date] || { ...o, date, score: null })
        );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Scott Sauyet
Scott Sauyet

Reputation: 50787

Here's one approach. We calculate the list of unique dates, group the records by name, and then flatMap that result into arrays of elements formed by mapping the dates and choosing the existing match or creating a new one from a simple template based on the first record for that person:

const addMissingDates = (records) => {
  const dates = [... new Set(records .map (r => r.date))]
  const byName = Object .values (records .reduce (
    (a, x) => ({...a, [x.name]: [...(a[x.name] || []), x]}), 
    {}
  ))
  return byName .flatMap (person => {
    const {score, date, ...rest} = person[0] 
    return dates .map (
      d => person .find (({date}) => d == date) || {...rest, date: d, score: null}
    )
  })
}

const myArray = [{name: "Dave", date: "2020-07-30T17:30:46.180Z", score: 50, age: 20}, {name: "Dave", date: "2020-06-30T17:30:26.160Z", score: 35, age: 20}, {name: "Cosmo", date: "2020-06-30T17:30:26.160Z", score: 65, age: 22}, {name: "Dave", date: "2020-05-30T15:30:16.160Z", score: 65, age: 20}, {name: "Carlos", date: "2020-07-30T17:30:46.180Z", score: 70, age: 28}, {name: "Carlos", date: "2020-06-30T17:30:26.160Z", score: 53, age: 28},]

console .log (addMissingDates (myArray))
.as-console-wrapper {max-height: 100% !important; top: 0}

Update: fixed a bug and added a simplification inspired by uke's answer.

Update 2: Another implementation of the same idea, using my preferred expressions-rather-than-statements style:

const addMissingDates = (records, dates = [... new Set(records .map (r => r.date))]) => 
  Object .values (records .reduce (
    (a, x) => ({...a, [x.name]: [...(a[x.name] || []), x]}), 
    {}
  )) .flatMap ((person, _, __, {score, date, ...rest} = person [0]) => dates .map (
    d => person .find (({date}) => d == date) || {...rest, date: d, score: null}
  ))

Upvotes: 1

sid c
sid c

Reputation: 195

To check if a property is in an object, use the in operator. For example:

if (!("date" in el)) {
    // executes if there is no date property.
}

Upvotes: -1

Related Questions